tinybird 0.0.1.dev212__py3-none-any.whl → 0.0.1.dev214__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/{tb/modules/datafile → datafile}/common.py +26 -26
- tinybird/datafile/parse_connection.py +60 -0
- tinybird/{tb/modules/datafile → datafile}/parse_datasource.py +3 -3
- tinybird/{tb/modules/datafile → datafile}/parse_pipe.py +3 -3
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -0
- tinybird/tb/client.py +3 -3
- tinybird/tb/modules/build.py +3 -3
- tinybird/tb/modules/common.py +4 -2
- tinybird/tb/modules/connection.py +30 -5
- tinybird/tb/modules/copy.py +2 -43
- tinybird/tb/modules/datafile/build.py +15 -15
- tinybird/tb/modules/datafile/build_datasource.py +1 -1
- tinybird/tb/modules/datafile/build_pipe.py +1 -1
- tinybird/tb/modules/datafile/diff.py +1 -1
- tinybird/tb/modules/datafile/format_common.py +1 -1
- tinybird/tb/modules/datafile/format_datasource.py +2 -2
- tinybird/tb/modules/datafile/format_pipe.py +3 -3
- tinybird/tb/modules/datafile/pipe_checker.py +1 -1
- tinybird/tb/modules/datafile/playground.py +17 -17
- tinybird/tb/modules/datasource.py +1 -1
- tinybird/tb/modules/endpoint.py +1 -1
- tinybird/tb/modules/feedback_manager.py +9 -1
- tinybird/tb/modules/local.py +8 -237
- tinybird/tb/modules/local_common.py +5 -1
- tinybird/tb/modules/materialization.py +1 -1
- tinybird/tb/modules/pipe.py +1 -1
- tinybird/tb/modules/project.py +5 -3
- tinybird/tb/modules/sink.py +105 -0
- tinybird/tb/modules/watch.py +1 -1
- {tinybird-0.0.1.dev212.dist-info → tinybird-0.0.1.dev214.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev212.dist-info → tinybird-0.0.1.dev214.dist-info}/RECORD +36 -34
- /tinybird/{tb/modules/datafile → datafile}/exceptions.py +0 -0
- {tinybird-0.0.1.dev212.dist-info → tinybird-0.0.1.dev214.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev212.dist-info → tinybird-0.0.1.dev214.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev212.dist-info → tinybird-0.0.1.dev214.dist-info}/top_level.txt +0 -0
|
@@ -11,20 +11,7 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
|
11
11
|
import click
|
|
12
12
|
from toposort import toposort
|
|
13
13
|
|
|
14
|
-
from tinybird.
|
|
15
|
-
from tinybird.sql_template import get_used_tables_in_template, render_sql_template
|
|
16
|
-
from tinybird.tb.client import TinyB
|
|
17
|
-
from tinybird.tb.modules.common import get_ca_pem_content
|
|
18
|
-
from tinybird.tb.modules.config import CLIConfig
|
|
19
|
-
from tinybird.tb.modules.datafile.build_datasource import is_datasource
|
|
20
|
-
from tinybird.tb.modules.datafile.build_pipe import (
|
|
21
|
-
get_target_materialized_data_source_name,
|
|
22
|
-
is_endpoint,
|
|
23
|
-
is_endpoint_with_no_dependencies,
|
|
24
|
-
is_materialized,
|
|
25
|
-
new_pipe,
|
|
26
|
-
)
|
|
27
|
-
from tinybird.tb.modules.datafile.common import (
|
|
14
|
+
from tinybird.datafile.common import (
|
|
28
15
|
DEFAULT_CRON_PERIOD,
|
|
29
16
|
INTERNAL_TABLES,
|
|
30
17
|
ON_DEMAND,
|
|
@@ -40,9 +27,22 @@ from tinybird.tb.modules.datafile.common import (
|
|
|
40
27
|
get_project_filenames,
|
|
41
28
|
pp,
|
|
42
29
|
)
|
|
43
|
-
from tinybird.
|
|
44
|
-
from tinybird.
|
|
45
|
-
from tinybird.
|
|
30
|
+
from tinybird.datafile.exceptions import AlreadyExistsException, IncludeFileNotFoundException
|
|
31
|
+
from tinybird.datafile.parse_datasource import parse_datasource
|
|
32
|
+
from tinybird.datafile.parse_pipe import parse_pipe
|
|
33
|
+
from tinybird.sql import parse_table_structure, schema_to_sql_columns
|
|
34
|
+
from tinybird.sql_template import get_used_tables_in_template, render_sql_template
|
|
35
|
+
from tinybird.tb.client import TinyB
|
|
36
|
+
from tinybird.tb.modules.common import get_ca_pem_content
|
|
37
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
38
|
+
from tinybird.tb.modules.datafile.build_datasource import is_datasource
|
|
39
|
+
from tinybird.tb.modules.datafile.build_pipe import (
|
|
40
|
+
get_target_materialized_data_source_name,
|
|
41
|
+
is_endpoint,
|
|
42
|
+
is_endpoint_with_no_dependencies,
|
|
43
|
+
is_materialized,
|
|
44
|
+
new_pipe,
|
|
45
|
+
)
|
|
46
46
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
47
47
|
from tinybird.tb.modules.project import Project
|
|
48
48
|
|
|
@@ -18,6 +18,7 @@ import humanfriendly
|
|
|
18
18
|
import requests
|
|
19
19
|
from click import Context
|
|
20
20
|
|
|
21
|
+
from tinybird.datafile.common import get_name_version
|
|
21
22
|
from tinybird.prompts import quarantine_prompt
|
|
22
23
|
from tinybird.syncasync import sync_to_async
|
|
23
24
|
from tinybird.tb.client import AuthNoTokenException, DoesNotExistException, TinyB
|
|
@@ -40,7 +41,6 @@ from tinybird.tb.modules.create import (
|
|
|
40
41
|
generate_aws_iamrole_connection_file_with_secret,
|
|
41
42
|
generate_gcs_connection_file_with_secrets,
|
|
42
43
|
)
|
|
43
|
-
from tinybird.tb.modules.datafile.common import get_name_version
|
|
44
44
|
from tinybird.tb.modules.datafile.fixture import persist_fixture
|
|
45
45
|
from tinybird.tb.modules.exceptions import CLIDatasourceException
|
|
46
46
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
tinybird/tb/modules/endpoint.py
CHANGED
|
@@ -14,10 +14,10 @@ import pyperclip
|
|
|
14
14
|
import requests
|
|
15
15
|
from click import Context
|
|
16
16
|
|
|
17
|
+
from tinybird.datafile.common import get_name_version
|
|
17
18
|
from tinybird.tb.client import AuthNoTokenException, DoesNotExistException, TinyB
|
|
18
19
|
from tinybird.tb.modules.cli import cli
|
|
19
20
|
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table
|
|
20
|
-
from tinybird.tb.modules.datafile.common import get_name_version
|
|
21
21
|
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
22
22
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
23
23
|
|
|
@@ -587,7 +587,7 @@ STEP {step}: CREATE IAM ROLE FOR {environment} ENVIRONMENT
|
|
|
587
587
|
Please enter the ARN of the role you just created"""
|
|
588
588
|
)
|
|
589
589
|
|
|
590
|
-
|
|
590
|
+
prompt_s3_iamrole_success_read = success_message("""
|
|
591
591
|
✅ S3 CONNECTION CONFIGURED SUCCESSFULLY
|
|
592
592
|
|
|
593
593
|
• File created at: {connection_path}
|
|
@@ -595,6 +595,14 @@ Please enter the ARN of the role you just created"""
|
|
|
595
595
|
• Learn more about our S3 Connector: https://www.tinybird.co/docs/forward/get-data-in/connectors/s3
|
|
596
596
|
""")
|
|
597
597
|
|
|
598
|
+
prompt_s3_iamrole_success_write = success_message("""
|
|
599
|
+
✅ S3 CONNECTION CONFIGURED SUCCESSFULLY
|
|
600
|
+
|
|
601
|
+
• File created at: {connection_path}
|
|
602
|
+
• You can now use this connection in your Data Sources with: EXPORT_CONNECTION_NAME '{connection_name}'
|
|
603
|
+
• Learn more about our S3 Sinks: https://www.tinybird.co/docs/forward/work-with-data/publish-data/s3-sinks
|
|
604
|
+
""")
|
|
605
|
+
|
|
598
606
|
# S3 IAM Role Connection Messages
|
|
599
607
|
prompt_gcs_connection_header = info_highlight_message("""
|
|
600
608
|
──────────────────────────────────────────────────────────────
|
tinybird/tb/modules/local.py
CHANGED
|
@@ -1,249 +1,20 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import re
|
|
4
1
|
import subprocess
|
|
5
|
-
import time
|
|
6
|
-
from typing import Optional
|
|
7
2
|
|
|
8
|
-
import boto3
|
|
9
3
|
import click
|
|
10
4
|
import requests
|
|
11
5
|
|
|
12
|
-
import docker
|
|
13
6
|
from docker.client import DockerClient
|
|
14
|
-
from docker.models.containers import Container
|
|
15
7
|
from tinybird.tb.modules.cli import cli
|
|
16
8
|
from tinybird.tb.modules.common import coro
|
|
17
|
-
from tinybird.tb.modules.exceptions import CLIException
|
|
9
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
18
10
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
19
|
-
from tinybird.tb.modules.local_common import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
) -> None:
|
|
27
|
-
"""Start the Tinybird container."""
|
|
28
|
-
pull_show_prompt = False
|
|
29
|
-
pull_required = False
|
|
30
|
-
try:
|
|
31
|
-
local_image = docker_client.images.get(TB_IMAGE_NAME)
|
|
32
|
-
local_image_id = local_image.attrs["RepoDigests"][0].split("@")[1]
|
|
33
|
-
remote_image = docker_client.images.get_registry_data(TB_IMAGE_NAME)
|
|
34
|
-
pull_show_prompt = local_image_id != remote_image.id
|
|
35
|
-
except Exception:
|
|
36
|
-
pull_show_prompt = False
|
|
37
|
-
pull_required = True
|
|
38
|
-
|
|
39
|
-
if pull_show_prompt and click.confirm(
|
|
40
|
-
FeedbackManager.warning(message="△ New version detected, download? [y/N]:"),
|
|
41
|
-
show_default=False,
|
|
42
|
-
prompt_suffix="",
|
|
43
|
-
):
|
|
44
|
-
click.echo(FeedbackManager.info(message="* Downloading latest version of Tinybird Local..."))
|
|
45
|
-
pull_required = True
|
|
46
|
-
|
|
47
|
-
if pull_required:
|
|
48
|
-
docker_client.images.pull(TB_IMAGE_NAME, platform="linux/amd64")
|
|
49
|
-
|
|
50
|
-
environment = get_use_aws_creds() if use_aws_creds else {}
|
|
51
|
-
|
|
52
|
-
container = get_existing_container_with_matching_env(docker_client, TB_CONTAINER_NAME, environment)
|
|
53
|
-
|
|
54
|
-
if container and not pull_required:
|
|
55
|
-
# Container `start` is idempotent. It's safe to call it even if the container is already running.
|
|
56
|
-
container.start()
|
|
57
|
-
else:
|
|
58
|
-
if container:
|
|
59
|
-
container.remove(force=True)
|
|
60
|
-
|
|
61
|
-
container = docker_client.containers.run(
|
|
62
|
-
TB_IMAGE_NAME,
|
|
63
|
-
name=TB_CONTAINER_NAME,
|
|
64
|
-
detach=True,
|
|
65
|
-
ports={"7181/tcp": TB_LOCAL_PORT},
|
|
66
|
-
remove=False,
|
|
67
|
-
platform="linux/amd64",
|
|
68
|
-
environment=environment,
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
click.echo(FeedbackManager.info(message="* Waiting for Tinybird Local to be ready..."))
|
|
72
|
-
while True:
|
|
73
|
-
container.reload() # Refresh container attributes
|
|
74
|
-
health = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
|
75
|
-
if health == "healthy":
|
|
76
|
-
break
|
|
77
|
-
if health == "unhealthy":
|
|
78
|
-
raise CLILocalException(
|
|
79
|
-
FeedbackManager.error(
|
|
80
|
-
message="Tinybird Local is unhealthy. Try running `tb local restart` in a few seconds."
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
time.sleep(5)
|
|
85
|
-
|
|
86
|
-
# Remove tinybird-local dangling images to avoid running out of disk space
|
|
87
|
-
images = docker_client.images.list(name=re.sub(r":.*$", "", TB_IMAGE_NAME), all=True, filters={"dangling": True})
|
|
88
|
-
for image in images:
|
|
89
|
-
image.remove(force=True)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def get_existing_container_with_matching_env(
|
|
93
|
-
docker_client: DockerClient, container_name: str, required_env: dict[str, str]
|
|
94
|
-
) -> Optional[Container]:
|
|
95
|
-
"""
|
|
96
|
-
Checks if a container with the given name exists and has matching environment variables.
|
|
97
|
-
If it exists but environment doesn't match, it returns None.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
docker_client: The Docker client instance
|
|
101
|
-
container_name: The name of the container to check
|
|
102
|
-
required_env: Dictionary of environment variables that must be present
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
The container if it exists with matching environment, None otherwise
|
|
106
|
-
"""
|
|
107
|
-
container = None
|
|
108
|
-
containers = docker_client.containers.list(all=True, filters={"name": container_name})
|
|
109
|
-
if containers:
|
|
110
|
-
container = containers[0]
|
|
111
|
-
|
|
112
|
-
if container and required_env:
|
|
113
|
-
container_info = container.attrs
|
|
114
|
-
container_env = container_info.get("Config", {}).get("Env", [])
|
|
115
|
-
env_missing = False
|
|
116
|
-
for key, value in required_env.items():
|
|
117
|
-
env_var = f"{key}={value}"
|
|
118
|
-
if env_var not in container_env:
|
|
119
|
-
env_missing = True
|
|
120
|
-
break
|
|
121
|
-
|
|
122
|
-
if env_missing:
|
|
123
|
-
container.remove(force=True)
|
|
124
|
-
container = None
|
|
125
|
-
|
|
126
|
-
return container
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def get_docker_client() -> DockerClient:
|
|
130
|
-
"""Check if Docker is installed and running."""
|
|
131
|
-
try:
|
|
132
|
-
docker_host = os.getenv("DOCKER_HOST")
|
|
133
|
-
if not docker_host:
|
|
134
|
-
# Try to get docker host from docker context
|
|
135
|
-
try:
|
|
136
|
-
try:
|
|
137
|
-
output = subprocess.check_output(["docker", "context", "inspect"], text=True)
|
|
138
|
-
except Exception as e:
|
|
139
|
-
add_telemetry_event(
|
|
140
|
-
"docker_error",
|
|
141
|
-
error=f"docker_context_inspect_error: {str(e)}",
|
|
142
|
-
)
|
|
143
|
-
raise e
|
|
144
|
-
try:
|
|
145
|
-
context = json.loads(output)
|
|
146
|
-
except Exception as e:
|
|
147
|
-
add_telemetry_event(
|
|
148
|
-
"docker_error",
|
|
149
|
-
error=f"docker_context_inspect_parse_output_error: {str(e)}",
|
|
150
|
-
data={
|
|
151
|
-
"docker_context_inspect_output": output,
|
|
152
|
-
},
|
|
153
|
-
)
|
|
154
|
-
raise e
|
|
155
|
-
if context and len(context) > 0:
|
|
156
|
-
try:
|
|
157
|
-
docker_host = context[0].get("Endpoints", {}).get("docker", {}).get("Host")
|
|
158
|
-
if docker_host:
|
|
159
|
-
os.environ["DOCKER_HOST"] = docker_host
|
|
160
|
-
except Exception as e:
|
|
161
|
-
add_telemetry_event(
|
|
162
|
-
"docker_error",
|
|
163
|
-
error=f"docker_context_parse_host_error: {str(e)}",
|
|
164
|
-
data={
|
|
165
|
-
"context": json.dumps(context),
|
|
166
|
-
},
|
|
167
|
-
)
|
|
168
|
-
raise e
|
|
169
|
-
except Exception:
|
|
170
|
-
pass
|
|
171
|
-
try:
|
|
172
|
-
client = docker.from_env() # type: ignore
|
|
173
|
-
except Exception as e:
|
|
174
|
-
add_telemetry_event(
|
|
175
|
-
"docker_error",
|
|
176
|
-
error=f"docker_get_client_from_env_error: {str(e)}",
|
|
177
|
-
)
|
|
178
|
-
raise e
|
|
179
|
-
try:
|
|
180
|
-
client.ping()
|
|
181
|
-
except Exception as e:
|
|
182
|
-
client_dict_non_sensitive = {k: v for k, v in client.api.__dict__.items() if "auth" not in k}
|
|
183
|
-
add_telemetry_event(
|
|
184
|
-
"docker_error",
|
|
185
|
-
error=f"docker_ping_error: {str(e)}",
|
|
186
|
-
data={
|
|
187
|
-
"client": repr(client_dict_non_sensitive),
|
|
188
|
-
},
|
|
189
|
-
)
|
|
190
|
-
raise e
|
|
191
|
-
return client
|
|
192
|
-
except Exception:
|
|
193
|
-
docker_location_message = ""
|
|
194
|
-
if docker_host:
|
|
195
|
-
docker_location_message = f"Trying to connect to Docker-compatible runtime at {docker_host}"
|
|
196
|
-
|
|
197
|
-
raise CLILocalException(
|
|
198
|
-
FeedbackManager.error(
|
|
199
|
-
message=(
|
|
200
|
-
f"No container runtime is running. Make sure a Docker-compatible runtime is installed and running. "
|
|
201
|
-
f"{docker_location_message}\n\n"
|
|
202
|
-
"If you're using a custom location, please provide it using the DOCKER_HOST environment variable."
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def get_use_aws_creds() -> dict[str, str]:
|
|
209
|
-
credentials: dict[str, str] = {}
|
|
210
|
-
try:
|
|
211
|
-
# Get the boto3 session and credentials
|
|
212
|
-
session = boto3.Session()
|
|
213
|
-
creds = session.get_credentials()
|
|
214
|
-
|
|
215
|
-
if creds:
|
|
216
|
-
# Create environment variables for the container based on boto credentials
|
|
217
|
-
credentials["AWS_ACCESS_KEY_ID"] = creds.access_key
|
|
218
|
-
credentials["AWS_SECRET_ACCESS_KEY"] = creds.secret_key
|
|
219
|
-
|
|
220
|
-
# Add session token if it exists (for temporary credentials)
|
|
221
|
-
if creds.token:
|
|
222
|
-
credentials["AWS_SESSION_TOKEN"] = creds.token
|
|
223
|
-
|
|
224
|
-
# Add region if available
|
|
225
|
-
if session.region_name:
|
|
226
|
-
credentials["AWS_DEFAULT_REGION"] = session.region_name
|
|
227
|
-
|
|
228
|
-
click.echo(
|
|
229
|
-
FeedbackManager.success(
|
|
230
|
-
message=f"✓ AWS credentials found and will be passed to Tinybird Local (region: {session.region_name or 'not set'})"
|
|
231
|
-
)
|
|
232
|
-
)
|
|
233
|
-
else:
|
|
234
|
-
click.echo(
|
|
235
|
-
FeedbackManager.warning(
|
|
236
|
-
message="△ No AWS credentials found. S3 operations will not work in Tinybird Local."
|
|
237
|
-
)
|
|
238
|
-
)
|
|
239
|
-
except Exception as e:
|
|
240
|
-
click.echo(
|
|
241
|
-
FeedbackManager.warning(
|
|
242
|
-
message=f"△ Error retrieving AWS credentials: {str(e)}. S3 operations will not work in Tinybird Local."
|
|
243
|
-
)
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
return credentials
|
|
11
|
+
from tinybird.tb.modules.local_common import (
|
|
12
|
+
TB_CONTAINER_NAME,
|
|
13
|
+
TB_LOCAL_ADDRESS,
|
|
14
|
+
get_docker_client,
|
|
15
|
+
get_existing_container_with_matching_env,
|
|
16
|
+
start_tinybird_local,
|
|
17
|
+
)
|
|
247
18
|
|
|
248
19
|
|
|
249
20
|
def stop_tinybird_local(docker_client: DockerClient) -> None:
|
|
@@ -384,11 +384,15 @@ def get_docker_client() -> DockerClient:
|
|
|
384
384
|
raise e
|
|
385
385
|
return client
|
|
386
386
|
except Exception:
|
|
387
|
+
docker_location_message = ""
|
|
388
|
+
if docker_host:
|
|
389
|
+
docker_location_message = f"Trying to connect to Docker-compatible runtime at {docker_host}"
|
|
390
|
+
|
|
387
391
|
raise CLILocalException(
|
|
388
392
|
FeedbackManager.error(
|
|
389
393
|
message=(
|
|
390
394
|
f"No container runtime is running. Make sure a Docker-compatible runtime is installed and running. "
|
|
391
|
-
f"
|
|
395
|
+
f"{docker_location_message}\n\n"
|
|
392
396
|
"If you're using a custom location, please provide it using the DOCKER_HOST environment variable."
|
|
393
397
|
)
|
|
394
398
|
)
|
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
|
+
from tinybird.datafile.common import PipeTypes, get_name_version
|
|
6
7
|
from tinybird.tb.client import TinyB
|
|
7
8
|
from tinybird.tb.modules.cli import cli
|
|
8
9
|
from tinybird.tb.modules.common import (
|
|
@@ -10,7 +11,6 @@ from tinybird.tb.modules.common import (
|
|
|
10
11
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
11
12
|
wait_job,
|
|
12
13
|
)
|
|
13
|
-
from tinybird.tb.modules.datafile.common import PipeTypes, get_name_version
|
|
14
14
|
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
15
15
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
16
16
|
|
tinybird/tb/modules/pipe.py
CHANGED
|
@@ -9,13 +9,13 @@ import re
|
|
|
9
9
|
import click
|
|
10
10
|
from click import Context
|
|
11
11
|
|
|
12
|
+
from tinybird.datafile.common import get_name_version
|
|
12
13
|
from tinybird.tb.client import TinyB
|
|
13
14
|
from tinybird.tb.modules.cli import cli
|
|
14
15
|
from tinybird.tb.modules.common import (
|
|
15
16
|
coro,
|
|
16
17
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
17
18
|
)
|
|
18
|
-
from tinybird.tb.modules.datafile.common import get_name_version
|
|
19
19
|
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
20
20
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
21
21
|
|
tinybird/tb/modules/project.py
CHANGED
|
@@ -3,9 +3,9 @@ import re
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Dict, List, Optional
|
|
5
5
|
|
|
6
|
-
from tinybird.
|
|
7
|
-
from tinybird.
|
|
8
|
-
from tinybird.
|
|
6
|
+
from tinybird.datafile.common import Datafile
|
|
7
|
+
from tinybird.datafile.parse_datasource import parse_datasource
|
|
8
|
+
from tinybird.datafile.parse_pipe import parse_pipe
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Project:
|
|
@@ -125,6 +125,8 @@ class Project:
|
|
|
125
125
|
return "materialization"
|
|
126
126
|
elif re.search(r"TYPE copy", content, re.IGNORECASE):
|
|
127
127
|
return "copy"
|
|
128
|
+
elif re.search(r"TYPE sink", content, re.IGNORECASE):
|
|
129
|
+
return "sink"
|
|
128
130
|
return "pipe"
|
|
129
131
|
except Exception:
|
|
130
132
|
return "pipe"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import Context
|
|
7
|
+
|
|
8
|
+
from tinybird.datafile.common import get_name_version
|
|
9
|
+
from tinybird.tb.client import AuthNoTokenException, TinyB
|
|
10
|
+
from tinybird.tb.modules.cli import cli
|
|
11
|
+
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table, wait_job
|
|
12
|
+
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
13
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cli.group()
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def sink(ctx):
|
|
19
|
+
"""Sink pipe commands."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@sink.command(name="ls")
|
|
23
|
+
@click.option("--match", default=None, help="Retrieve any resourcing matching the pattern. eg --match _test")
|
|
24
|
+
@click.option(
|
|
25
|
+
"--format",
|
|
26
|
+
"format_",
|
|
27
|
+
type=click.Choice(["json"], case_sensitive=False),
|
|
28
|
+
default=None,
|
|
29
|
+
help="Force a type of the output",
|
|
30
|
+
)
|
|
31
|
+
@click.pass_context
|
|
32
|
+
@coro
|
|
33
|
+
async def sink_ls(ctx: Context, match: str, format_: str):
|
|
34
|
+
"""List sink pipes"""
|
|
35
|
+
|
|
36
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
37
|
+
pipes = await client.pipes(dependencies=False, node_attrs="name", attrs="name,updated_at,type")
|
|
38
|
+
sinks = [p for p in pipes if p.get("type") == "sink"]
|
|
39
|
+
sinks = sorted(sinks, key=lambda p: p["updated_at"])
|
|
40
|
+
columns = ["name", "updated at", "nodes"]
|
|
41
|
+
table_human_readable = []
|
|
42
|
+
table_machine_readable = []
|
|
43
|
+
pattern = re.compile(match) if match else None
|
|
44
|
+
for t in sinks:
|
|
45
|
+
tk = get_name_version(t["name"])
|
|
46
|
+
if pattern and not pattern.search(tk["name"]):
|
|
47
|
+
continue
|
|
48
|
+
table_human_readable.append((tk["name"], t["updated_at"][:-7], len(t["nodes"])))
|
|
49
|
+
table_machine_readable.append(
|
|
50
|
+
{
|
|
51
|
+
"name": tk["name"],
|
|
52
|
+
"updated at": t["updated_at"][:-7],
|
|
53
|
+
"nodes": len(t["nodes"]),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if not format_:
|
|
58
|
+
click.echo(FeedbackManager.info_pipes())
|
|
59
|
+
echo_safe_humanfriendly_tables_format_smart_table(table_human_readable, column_names=columns)
|
|
60
|
+
click.echo("\n")
|
|
61
|
+
elif format_ == "json":
|
|
62
|
+
click.echo(json.dumps({"pipes": table_machine_readable}, indent=2))
|
|
63
|
+
else:
|
|
64
|
+
raise CLIPipeException(FeedbackManager.error_pipe_ls_type())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@sink.command(name="run", short_help="Run an on-demand sink job")
|
|
68
|
+
@click.argument("pipe_name_or_id")
|
|
69
|
+
@click.option("--wait", is_flag=True, default=False, help="Wait for the sink job to finish")
|
|
70
|
+
@click.option(
|
|
71
|
+
"--mode", type=click.Choice(["append", "replace"], case_sensitive=True), default=None, help="Sink strategy"
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--param",
|
|
75
|
+
nargs=1,
|
|
76
|
+
type=str,
|
|
77
|
+
multiple=True,
|
|
78
|
+
default=None,
|
|
79
|
+
help="Key and value of the params you want the Sink pipe to be called with. For example: tb pipe sink run <my_sink_pipe> --param foo=bar",
|
|
80
|
+
)
|
|
81
|
+
@click.pass_context
|
|
82
|
+
@coro
|
|
83
|
+
async def sink_run(ctx: click.Context, pipe_name_or_id: str, wait: bool, mode: str, param: Optional[Tuple[str]]):
|
|
84
|
+
"""Run a sink pipe"""
|
|
85
|
+
|
|
86
|
+
params = dict(key_value.split("=") for key_value in param) if param else {}
|
|
87
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Running sink '{pipe_name_or_id}'"))
|
|
88
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
response = await client.pipe_run(pipe_name_or_id, "sink", params, mode)
|
|
92
|
+
job_id = response["job"]["id"]
|
|
93
|
+
job_url = response["job"]["job_url"]
|
|
94
|
+
click.echo(FeedbackManager.gray(message="Job URL: ") + FeedbackManager.info(message=f"{job_url}"))
|
|
95
|
+
click.echo(FeedbackManager.success(message="✓ Sink job created"))
|
|
96
|
+
|
|
97
|
+
if wait:
|
|
98
|
+
click.echo("\n")
|
|
99
|
+
await wait_job(client, job_id, job_url, FeedbackManager.highlight(message="» Exporting data"))
|
|
100
|
+
click.echo(FeedbackManager.success(message="✓ Data exported"))
|
|
101
|
+
|
|
102
|
+
except AuthNoTokenException:
|
|
103
|
+
raise
|
|
104
|
+
except Exception as e:
|
|
105
|
+
raise CLIPipeException(FeedbackManager.error_creating_sink_job(error=e))
|
tinybird/tb/modules/watch.py
CHANGED
|
@@ -15,7 +15,7 @@ from watchdog.events import (
|
|
|
15
15
|
)
|
|
16
16
|
from watchdog.observers import Observer
|
|
17
17
|
|
|
18
|
-
from tinybird.
|
|
18
|
+
from tinybird.datafile.common import Datafile, DatafileKind
|
|
19
19
|
from tinybird.tb.modules.datafile.fixture import FixtureExtension
|
|
20
20
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
21
21
|
from tinybird.tb.modules.project import Project
|