tinybird 0.0.1.dev5__py3-none-any.whl → 0.0.1.dev6__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/__cli__.py +7 -8
- tinybird/tb/cli.py +28 -0
- tinybird/{tb_cli_modules → tb/modules}/auth.py +5 -5
- tinybird/{tb_cli_modules → tb/modules}/branch.py +6 -5
- tinybird/{tb_cli_modules → tb/modules}/build.py +8 -8
- tinybird/tb/modules/cicd.py +271 -0
- tinybird/{tb_cli_modules → tb/modules}/cli.py +23 -23
- tinybird/tb/modules/common.py +2098 -0
- tinybird/tb/modules/config.py +352 -0
- tinybird/{tb_cli_modules → tb/modules}/connection.py +4 -4
- tinybird/{tb_cli_modules → tb/modules}/create.py +11 -7
- tinybird/{datafile.py → tb/modules/datafile.py} +6 -7
- tinybird/{tb_cli_modules → tb/modules}/datasource.py +7 -6
- tinybird/tb/modules/exceptions.py +91 -0
- tinybird/{tb_cli_modules → tb/modules}/fmt.py +3 -3
- tinybird/{tb_cli_modules → tb/modules}/job.py +3 -3
- tinybird/{tb_cli_modules → tb/modules}/llm.py +1 -1
- tinybird/{tb_cli_modules → tb/modules}/local.py +9 -5
- tinybird/{tb_cli_modules → tb/modules}/mock.py +5 -5
- tinybird/{tb_cli_modules → tb/modules}/pipe.py +5 -5
- tinybird/tb/modules/regions.py +9 -0
- tinybird/{tb_cli_modules → tb/modules}/tag.py +2 -2
- tinybird/tb/modules/telemetry.py +310 -0
- tinybird/{tb_cli_modules → tb/modules}/test.py +5 -5
- tinybird/{tb_cli_modules → tb/modules}/tinyunit/tinyunit.py +1 -1
- tinybird/{tb_cli_modules → tb/modules}/token.py +3 -3
- tinybird/{tb_cli_modules → tb/modules}/workspace.py +5 -5
- tinybird/{tb_cli_modules → tb/modules}/workspace_members.py +4 -4
- tinybird/tb_cli_modules/common.py +9 -25
- tinybird/tb_cli_modules/config.py +0 -8
- {tinybird-0.0.1.dev5.dist-info → tinybird-0.0.1.dev6.dist-info}/METADATA +1 -1
- tinybird-0.0.1.dev6.dist-info/RECORD +58 -0
- tinybird-0.0.1.dev6.dist-info/entry_points.txt +2 -0
- tinybird/tb_cli.py +0 -28
- tinybird-0.0.1.dev5.dist-info/RECORD +0 -52
- tinybird-0.0.1.dev5.dist-info/entry_points.txt +0 -2
- /tinybird/{tb_cli_modules → tb/modules}/prompts.py +0 -0
- /tinybird/{tb_cli_modules → tb/modules}/table.py +0 -0
- /tinybird/{tb_cli_modules → tb/modules}/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev5.dist-info → tinybird-0.0.1.dev6.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev5.dist-info → tinybird-0.0.1.dev6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from packaging import version
|
|
10
|
+
|
|
11
|
+
import tinybird.client as tbc
|
|
12
|
+
from tinybird.config import CURRENT_VERSION, DEFAULT_API_HOST, DEFAULT_LOCALHOST
|
|
13
|
+
|
|
14
|
+
APP_CONFIG_NAME = "tinybird"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FeatureFlags:
|
|
18
|
+
@classmethod
|
|
19
|
+
def ignore_sql_errors(cls) -> bool: # Context: #1155
|
|
20
|
+
return "TB_IGNORE_SQL_ERRORS" in os.environ
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def is_localhost(cls) -> bool:
|
|
24
|
+
return "SET_LOCALHOST" in os.environ
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def ignore_ssl_errors(cls) -> bool:
|
|
28
|
+
return os.environ.get("TB_DISABLE_SSL_CHECKS", "0") == "1"
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def send_telemetry(cls) -> bool:
|
|
32
|
+
if os.environ.get("TB_CLI_TELEMETRY_OPTOUT", "0") == "1":
|
|
33
|
+
return False
|
|
34
|
+
if "x.y.z" in CURRENT_VERSION and os.environ.get("TB_CLI_TELEMETRY_SEND_IN_LOCAL", "0") == "0":
|
|
35
|
+
return False
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def compare_versions(a: str, b: str) -> int:
|
|
40
|
+
va = version.parse(a)
|
|
41
|
+
vb = version.parse(b)
|
|
42
|
+
return -1 if va < vb else (1 if va > vb else 0)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ConfigValueOrigin(Enum):
|
|
46
|
+
# Sources for config values (environment variables, .tinyb file or default value)
|
|
47
|
+
|
|
48
|
+
ENVIRONMENT: str = "env"
|
|
49
|
+
CONFIG: str = "conf"
|
|
50
|
+
DEFAULT: str = "default"
|
|
51
|
+
NONE: str = ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ConfigValue:
|
|
56
|
+
name: str
|
|
57
|
+
value: Any
|
|
58
|
+
origin: ConfigValueOrigin
|
|
59
|
+
|
|
60
|
+
def as_tuple(self) -> Tuple[str, str]:
|
|
61
|
+
return (self.name, self.value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def write_json_file(data: Dict[str, Any], path: str) -> None:
|
|
65
|
+
with open(path, "w") as file:
|
|
66
|
+
file.write(json.dumps(data, indent=4, sort_keys=True))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CLIConfig:
|
|
70
|
+
# Mapping between environment variables and config values
|
|
71
|
+
ENV_KEYS: Dict[str, str] = {
|
|
72
|
+
"token": "TB_TOKEN",
|
|
73
|
+
"user_token": "TB_USER_TOKEN",
|
|
74
|
+
"host": "TB_HOST",
|
|
75
|
+
"semver": "TB_SEMVER",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
DEFAULTS: Dict[str, str] = {"host": DEFAULT_API_HOST if not FeatureFlags.is_localhost() else DEFAULT_LOCALHOST}
|
|
79
|
+
|
|
80
|
+
_global: Optional["CLIConfig"] = None
|
|
81
|
+
_projects: Dict[str, "CLIConfig"] = {}
|
|
82
|
+
|
|
83
|
+
def __init__(self, path: Optional[str], parent: Optional["CLIConfig"] = None) -> None:
|
|
84
|
+
self._path = path
|
|
85
|
+
self._parent = parent
|
|
86
|
+
self._values: Dict[str, ConfigValue] = {}
|
|
87
|
+
self._values["version"] = ConfigValue("version", CURRENT_VERSION, ConfigValueOrigin.DEFAULT)
|
|
88
|
+
self._workspaces: List[Dict[str, Any]] = []
|
|
89
|
+
|
|
90
|
+
if self._path:
|
|
91
|
+
self.override_with_file(self._path)
|
|
92
|
+
self.override_with_environment()
|
|
93
|
+
self.override_with_defaults()
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return str(self.to_dict())
|
|
97
|
+
|
|
98
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
99
|
+
"""Helper to ease"""
|
|
100
|
+
result: Dict[str, Any] = self._parent.to_dict() if self._parent else {}
|
|
101
|
+
result.update(dict((v.name, deepcopy(v.value)) for v in self._values.values()))
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
def __getitem__(self, key) -> Any:
|
|
105
|
+
"""Gets a config key value in this order:
|
|
106
|
+
- Environment
|
|
107
|
+
- Internal dict (by host)
|
|
108
|
+
- Parent dict (if has a parent)
|
|
109
|
+
- Default values
|
|
110
|
+
"""
|
|
111
|
+
if key in self._values:
|
|
112
|
+
return self._values[key].value
|
|
113
|
+
if self._parent:
|
|
114
|
+
return self._parent[key]
|
|
115
|
+
raise KeyError(key)
|
|
116
|
+
|
|
117
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
118
|
+
self._values[key] = ConfigValue(key, value, ConfigValueOrigin.CONFIG)
|
|
119
|
+
|
|
120
|
+
def __contains__(self, key: str) -> bool:
|
|
121
|
+
return self.get_value_origin(key) != ConfigValueOrigin.NONE
|
|
122
|
+
|
|
123
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
124
|
+
try:
|
|
125
|
+
return self[key]
|
|
126
|
+
except KeyError:
|
|
127
|
+
return default
|
|
128
|
+
|
|
129
|
+
def get_value_origin(self, key: str) -> ConfigValueOrigin:
|
|
130
|
+
if key in self._values:
|
|
131
|
+
return self._values[key].origin
|
|
132
|
+
if self._parent:
|
|
133
|
+
return self._parent.get_value_origin(key)
|
|
134
|
+
else:
|
|
135
|
+
return ConfigValueOrigin.NONE
|
|
136
|
+
|
|
137
|
+
def persist_to_file(self, override_with_values: Optional["CLIConfig"] = None) -> None:
|
|
138
|
+
if not self._path:
|
|
139
|
+
raise ValueError("Cannot persist configuration: `path` is None")
|
|
140
|
+
|
|
141
|
+
if override_with_values:
|
|
142
|
+
self.update(override_with_values)
|
|
143
|
+
|
|
144
|
+
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
|
145
|
+
values = dict(v.as_tuple() for v in self._values.values())
|
|
146
|
+
write_json_file(values, self._path)
|
|
147
|
+
|
|
148
|
+
def override_with_file(self, path: str) -> bool:
|
|
149
|
+
"""Loads the contents of the passed file."""
|
|
150
|
+
try:
|
|
151
|
+
with open(path) as file:
|
|
152
|
+
values: Dict[str, Any] = json.loads(file.read())
|
|
153
|
+
for k, v in values.items():
|
|
154
|
+
self[k] = v
|
|
155
|
+
return True
|
|
156
|
+
except IOError:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def override_with_environment(self) -> None:
|
|
160
|
+
"""Loads environment variables."""
|
|
161
|
+
for config_key, env_key in CLIConfig.ENV_KEYS.items():
|
|
162
|
+
env_value = os.environ.get(env_key, None)
|
|
163
|
+
if env_value:
|
|
164
|
+
self._values[config_key] = ConfigValue(config_key, env_value, ConfigValueOrigin.ENVIRONMENT)
|
|
165
|
+
|
|
166
|
+
def override_with_defaults(self) -> None:
|
|
167
|
+
"""Loads default values."""
|
|
168
|
+
for key, default_value in CLIConfig.DEFAULTS.items():
|
|
169
|
+
if key not in self._values:
|
|
170
|
+
self._values[key] = ConfigValue(key, default_value, ConfigValueOrigin.DEFAULT)
|
|
171
|
+
|
|
172
|
+
def set_token(self, token: Optional[str]) -> None:
|
|
173
|
+
self["token"] = token
|
|
174
|
+
|
|
175
|
+
def get_token(self) -> Optional[str]:
|
|
176
|
+
try:
|
|
177
|
+
return self["token"]
|
|
178
|
+
except KeyError:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def set_semver(self, semver: Optional[str]) -> None:
|
|
182
|
+
self["semver"] = semver
|
|
183
|
+
|
|
184
|
+
def get_semver(self) -> Optional[str]:
|
|
185
|
+
try:
|
|
186
|
+
return self["semver"]
|
|
187
|
+
except KeyError:
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def set_token_for_host(self, token: Optional[str], host: Optional[str]) -> None:
|
|
191
|
+
"""Sets the token for the specified host.
|
|
192
|
+
|
|
193
|
+
Here we ask for the host explicitly to avoid mistakenly setting the token
|
|
194
|
+
for the wrong host.
|
|
195
|
+
"""
|
|
196
|
+
if not token and not host:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
assert isinstance(host, str)
|
|
200
|
+
|
|
201
|
+
tokens: Dict[str, Optional[str]] = self.get("tokens", {})
|
|
202
|
+
tokens[host] = token
|
|
203
|
+
self["tokens"] = tokens
|
|
204
|
+
|
|
205
|
+
def get_token_for_host(self, host: str) -> Optional[str]:
|
|
206
|
+
try:
|
|
207
|
+
return self["tokens"][host]
|
|
208
|
+
except KeyError:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def set_user_token(self, token: Optional[str]) -> None:
|
|
212
|
+
self["user_token"] = token
|
|
213
|
+
|
|
214
|
+
def get_user_token(self) -> Optional[str]:
|
|
215
|
+
try:
|
|
216
|
+
return self["user_token"]
|
|
217
|
+
except KeyError:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
def set_host(self, host: Optional[str]) -> None:
|
|
221
|
+
url_info = urlparse(host)
|
|
222
|
+
scheme: str = url_info.scheme.decode() if isinstance(url_info.scheme, bytes) else url_info.scheme
|
|
223
|
+
netloc: str = url_info.netloc.decode() if isinstance(url_info.netloc, bytes) else url_info.netloc
|
|
224
|
+
self["host"] = f"{scheme}://{netloc}"
|
|
225
|
+
|
|
226
|
+
def get_host(self, use_defaults_if_needed: bool = False) -> Optional[str]:
|
|
227
|
+
result: Optional[str] = self.get("host", None)
|
|
228
|
+
if result:
|
|
229
|
+
return result
|
|
230
|
+
if use_defaults_if_needed:
|
|
231
|
+
return CLIConfig.DEFAULTS["host"]
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def get_client(self, token: Optional[str] = None, host: Optional[str] = None) -> tbc.TinyB:
|
|
235
|
+
"""Returns a new TinyB client configured with:
|
|
236
|
+
|
|
237
|
+
- token:
|
|
238
|
+
- passed token
|
|
239
|
+
- OR the user token
|
|
240
|
+
- OR the current admin token
|
|
241
|
+
|
|
242
|
+
- host:
|
|
243
|
+
- passed host
|
|
244
|
+
- OR the current host
|
|
245
|
+
"""
|
|
246
|
+
host = host or self.get_host()
|
|
247
|
+
assert isinstance(host, str)
|
|
248
|
+
token = token or self.get_token() or self.get_token_for_host(host) or ""
|
|
249
|
+
assert isinstance(token, str)
|
|
250
|
+
|
|
251
|
+
return tbc.TinyB(
|
|
252
|
+
token,
|
|
253
|
+
host,
|
|
254
|
+
version=CURRENT_VERSION,
|
|
255
|
+
disable_ssl_checks=FeatureFlags.ignore_ssl_errors(),
|
|
256
|
+
send_telemetry=FeatureFlags.send_telemetry(),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
|
|
260
|
+
return self.get_client(self.get_user_token(), host)
|
|
261
|
+
|
|
262
|
+
def set_workspace_token(self, workspace_id: str, token: str) -> None:
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
async def get_workspace_token(
|
|
266
|
+
self, workspace_id: Optional[str] = None, host: Optional[str] = None
|
|
267
|
+
) -> Optional[str]:
|
|
268
|
+
"""Returns the token for the specific workspace on a host.
|
|
269
|
+
- If no workspace passed, it uses the current active workspace.
|
|
270
|
+
- If no host is passed, it uses the current one.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
# First, try to get any saved token for the host
|
|
274
|
+
if host:
|
|
275
|
+
try:
|
|
276
|
+
return self["tokens"][host]
|
|
277
|
+
except KeyError:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
# If we don't have an user_token, can't continue
|
|
281
|
+
if not self.get_user_token():
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
if not workspace_id:
|
|
285
|
+
workspace_id = self.get("id", None)
|
|
286
|
+
if not workspace_id:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
client: tbc.TinyB = self.get_client(token=self.get_user_token(), host=host)
|
|
290
|
+
|
|
291
|
+
info: Dict[str, Any] = await client.user_workspaces_and_branches()
|
|
292
|
+
workspaces: List[Dict[str, Any]] = info["workspaces"]
|
|
293
|
+
|
|
294
|
+
result: Optional[str] = next(
|
|
295
|
+
(w["token"] for w in workspaces if workspace_id in (w.get("id"), w.get("name"))), None
|
|
296
|
+
)
|
|
297
|
+
if host:
|
|
298
|
+
self.set_token_for_host(result, host)
|
|
299
|
+
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
def spawn(self) -> "CLIConfig":
|
|
303
|
+
return CLIConfig(path=None, parent=self)
|
|
304
|
+
|
|
305
|
+
def update(self, other: Union["CLIConfig", Dict[str, Any]]):
|
|
306
|
+
values = other if isinstance(other, dict) else other._values
|
|
307
|
+
for k, v in values.items():
|
|
308
|
+
self[k] = v
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def get_global_config(_path: Optional[str] = None) -> "CLIConfig":
|
|
312
|
+
"""Returns the user-specific config.
|
|
313
|
+
|
|
314
|
+
The data is cached between calls, so feel free to use it freely instead
|
|
315
|
+
of saving a reference.
|
|
316
|
+
|
|
317
|
+
Note: the `_path` argument is mainly intended to help during testing.
|
|
318
|
+
"""
|
|
319
|
+
if not CLIConfig._global:
|
|
320
|
+
path: Optional[str] = _path or os.environ.get("XDG_CONFIG_HOME", None)
|
|
321
|
+
if not path:
|
|
322
|
+
path = os.path.join(os.environ.get("HOME", "~"), ".config")
|
|
323
|
+
path = os.path.join(path, APP_CONFIG_NAME, ".tinyb")
|
|
324
|
+
CLIConfig._global = CLIConfig(path, parent=None)
|
|
325
|
+
return CLIConfig._global
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def get_project_config(working_dir: Optional[str] = None) -> "CLIConfig":
|
|
329
|
+
"""Returns the project-specific config located at `working_dir` (defaults to `os.getcwd()`)
|
|
330
|
+
|
|
331
|
+
The data is cached between calls, given the same `working_dir`.
|
|
332
|
+
"""
|
|
333
|
+
working_dir = working_dir or os.getcwd()
|
|
334
|
+
result = CLIConfig._projects.get(working_dir)
|
|
335
|
+
if not result:
|
|
336
|
+
path: str = os.path.join(working_dir, ".tinyb")
|
|
337
|
+
result = CLIConfig(path, parent=CLIConfig.get_global_config())
|
|
338
|
+
CLIConfig._projects[working_dir] = result
|
|
339
|
+
return result
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def get_llm_config(working_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
343
|
+
return (
|
|
344
|
+
CLIConfig.get_project_config(working_dir)
|
|
345
|
+
.get("llms", {})
|
|
346
|
+
.get("openai", {"model": "gpt-4o-mini", "api_key": None})
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def reset() -> None:
|
|
351
|
+
CLIConfig._global = None
|
|
352
|
+
CLIConfig._projects.clear()
|
|
@@ -14,8 +14,8 @@ from click import Context
|
|
|
14
14
|
|
|
15
15
|
from tinybird.client import DoesNotExistException, TinyB
|
|
16
16
|
from tinybird.feedback_manager import FeedbackManager
|
|
17
|
-
from tinybird.
|
|
18
|
-
from tinybird.
|
|
17
|
+
from tinybird.tb.modules.cli import cli
|
|
18
|
+
from tinybird.tb.modules.common import (
|
|
19
19
|
ConnectionReplacements,
|
|
20
20
|
DataConnectorType,
|
|
21
21
|
_get_setting_value,
|
|
@@ -33,8 +33,8 @@ from tinybird.tb_cli_modules.common import (
|
|
|
33
33
|
validate_kafka_secret,
|
|
34
34
|
validate_string_connector_param,
|
|
35
35
|
)
|
|
36
|
-
from tinybird.
|
|
37
|
-
from tinybird.
|
|
36
|
+
from tinybird.tb.modules.exceptions import CLIConnectionException
|
|
37
|
+
from tinybird.tb.modules.telemetry import is_ci_environment
|
|
38
38
|
|
|
39
39
|
DATA_CONNECTOR_SETTINGS: Dict[DataConnectorType, List[str]] = {
|
|
40
40
|
DataConnectorType.KAFKA: [
|
|
@@ -7,14 +7,15 @@ import click
|
|
|
7
7
|
from click import Context
|
|
8
8
|
|
|
9
9
|
from tinybird.client import TinyB
|
|
10
|
-
from tinybird.datafile import folder_build
|
|
11
10
|
from tinybird.feedback_manager import FeedbackManager
|
|
12
|
-
from tinybird.
|
|
13
|
-
from tinybird.
|
|
14
|
-
from tinybird.
|
|
15
|
-
from tinybird.
|
|
16
|
-
from tinybird.
|
|
17
|
-
from tinybird.
|
|
11
|
+
from tinybird.tb.modules.cicd import init_cicd
|
|
12
|
+
from tinybird.tb.modules.cli import cli
|
|
13
|
+
from tinybird.tb.modules.common import _generate_datafile, coro, generate_datafile, push_data
|
|
14
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
15
|
+
from tinybird.tb.modules.datafile import folder_build
|
|
16
|
+
from tinybird.tb.modules.exceptions import CLIDatasourceException
|
|
17
|
+
from tinybird.tb.modules.llm import LLM
|
|
18
|
+
from tinybird.tb.modules.local import (
|
|
18
19
|
get_tinybird_local_client,
|
|
19
20
|
)
|
|
20
21
|
|
|
@@ -63,6 +64,9 @@ async def create(
|
|
|
63
64
|
datasources,
|
|
64
65
|
pipes,
|
|
65
66
|
)
|
|
67
|
+
|
|
68
|
+
await init_cicd(data_project_dir=os.path.relpath(folder))
|
|
69
|
+
|
|
66
70
|
if data:
|
|
67
71
|
ds_name = os.path.basename(data.split(".")[0])
|
|
68
72
|
await append_datasource(ctx, tb_client, ds_name, data, None, None, False, 1)
|
|
@@ -58,7 +58,12 @@ from humanfriendly.tables import format_pretty_table
|
|
|
58
58
|
from mypy_extensions import KwArg, VarArg
|
|
59
59
|
from toposort import toposort
|
|
60
60
|
|
|
61
|
+
from tinybird.ch_utils.engine import ENABLED_ENGINES
|
|
62
|
+
from tinybird.client import AuthException, AuthNoTokenException, CanNotBeDeletedException, DoesNotExistException, TinyB
|
|
61
63
|
from tinybird.config import PROJECT_PATHS
|
|
64
|
+
from tinybird.feedback_manager import FeedbackManager
|
|
65
|
+
from tinybird.sql import parse_indexes_structure, parse_table_structure, schema_to_sql_columns
|
|
66
|
+
from tinybird.sql_template import get_template_and_variables, get_used_tables_in_template, render_sql_template
|
|
62
67
|
from tinybird.sql_template_fmt import DEFAULT_FMT_LINE_LENGTH, format_sql_template
|
|
63
68
|
from tinybird.syncasync import sync_to_async
|
|
64
69
|
from tinybird.tb_cli_modules.common import (
|
|
@@ -70,13 +75,7 @@ from tinybird.tb_cli_modules.common import (
|
|
|
70
75
|
)
|
|
71
76
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
72
77
|
from tinybird.tb_cli_modules.exceptions import CLIGitReleaseException, CLIPipeException
|
|
73
|
-
|
|
74
|
-
from .ch_utils.engine import ENABLED_ENGINES
|
|
75
|
-
from .client import AuthException, AuthNoTokenException, CanNotBeDeletedException, DoesNotExistException, TinyB
|
|
76
|
-
from .feedback_manager import FeedbackManager
|
|
77
|
-
from .sql import parse_indexes_structure, parse_table_structure, schema_to_sql_columns
|
|
78
|
-
from .sql_template import get_template_and_variables, get_used_tables_in_template, render_sql_template
|
|
79
|
-
from .tornado_template import UnClosedIfError
|
|
78
|
+
from tinybird.tornado_template import UnClosedIfError
|
|
80
79
|
|
|
81
80
|
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
|
82
81
|
from git import HEAD, Diff, GitCommandError, InvalidGitRepositoryError, Repo
|
|
@@ -15,16 +15,15 @@ from click import Context
|
|
|
15
15
|
|
|
16
16
|
from tinybird.client import AuthNoTokenException, CanNotBeDeletedException, DoesNotExistException, TinyB
|
|
17
17
|
from tinybird.config import get_display_host
|
|
18
|
-
from tinybird.
|
|
18
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
21
|
from tinybird.connectors import Connector
|
|
22
22
|
|
|
23
|
-
from tinybird.datafile import get_name_version, wait_job
|
|
24
23
|
from tinybird.feedback_manager import FeedbackManager
|
|
25
|
-
from tinybird.
|
|
26
|
-
from tinybird.
|
|
27
|
-
from tinybird.
|
|
24
|
+
from tinybird.tb.modules.branch import warn_if_in_live
|
|
25
|
+
from tinybird.tb.modules.cli import cli
|
|
26
|
+
from tinybird.tb.modules.common import (
|
|
28
27
|
_analyze,
|
|
29
28
|
_generate_datafile,
|
|
30
29
|
ask_for_user_token,
|
|
@@ -40,8 +39,10 @@ from tinybird.tb_cli_modules.common import (
|
|
|
40
39
|
validate_kafka_auto_offset_reset,
|
|
41
40
|
validate_kafka_group,
|
|
42
41
|
validate_kafka_topic,
|
|
42
|
+
wait_job,
|
|
43
43
|
)
|
|
44
|
-
from tinybird.
|
|
44
|
+
from tinybird.tb.modules.datafile import get_name_version
|
|
45
|
+
from tinybird.tb.modules.exceptions import CLIDatasourceException
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@cli.group()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from tinybird.tb.modules.telemetry import add_telemetry_event
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CLIException(click.exceptions.ClickException):
|
|
9
|
+
"""Default exception for all exceptions raised in the CLI.
|
|
10
|
+
|
|
11
|
+
Allows to specift a custom telemetry event to be sent before
|
|
12
|
+
raising the exception (if not specified, it sends a generic
|
|
13
|
+
`error` beacon with the error message)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str, telemetry_event: Optional[str] = None, **kw_telemetry_event_data: Any) -> None:
|
|
17
|
+
telemetry_event = telemetry_event or "error"
|
|
18
|
+
data: Dict[str, Any] = {"error": message}
|
|
19
|
+
data.update(kw_telemetry_event_data)
|
|
20
|
+
add_telemetry_event(telemetry_event, **data)
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CLIAuthException(CLIException):
|
|
25
|
+
"""Exceptions generated by the auth commands"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
28
|
+
super().__init__(message, "auth_error", **kw_telemetry_event_data)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CLIReleaseException(CLIException):
|
|
32
|
+
"""Exceptions generated by the release commands"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
35
|
+
super().__init__(message, "release_error", **kw_telemetry_event_data)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CLIBranchException(CLIException):
|
|
39
|
+
"""Exceptions generated by the branch commands"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
42
|
+
super().__init__(message, "branch_error", **kw_telemetry_event_data)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CLIGitReleaseException(CLIException):
|
|
46
|
+
"""Exceptions generated by the git release related commands"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
49
|
+
super().__init__(message, "git_release_error", **kw_telemetry_event_data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CLIConnectionException(CLIException):
|
|
53
|
+
"""Exceptions generated by the connection commands"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
56
|
+
super().__init__(message, "connection_error", **kw_telemetry_event_data)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CLIDatasourceException(CLIException):
|
|
60
|
+
"""Exceptions generated by the datasource commands"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
63
|
+
super().__init__(message, "datasource_error", **kw_telemetry_event_data)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CLIPipeException(CLIException):
|
|
67
|
+
"""Exceptions generated by the pipe commands"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
70
|
+
super().__init__(message, "pipe_error", **kw_telemetry_event_data)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CLIWorkspaceMembersException(CLIException):
|
|
74
|
+
"""Exceptions generated by the workspace members commands"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
77
|
+
super().__init__(message, "workspace_members_error", **kw_telemetry_event_data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CLIWorkspaceException(CLIException):
|
|
81
|
+
"""Exceptions generated by the workspace commands"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
84
|
+
super().__init__(message, "workspace_error", **kw_telemetry_event_data)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CLITokenException(CLIException):
|
|
88
|
+
"""Exceptions generated by the token commands"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
91
|
+
super().__init__(message, "token_error", **kw_telemetry_event_data)
|
|
@@ -7,10 +7,10 @@ import aiofiles
|
|
|
7
7
|
import click
|
|
8
8
|
from click import Context
|
|
9
9
|
|
|
10
|
-
from tinybird.datafile import color_diff, format_datasource, format_pipe, is_file_a_datasource, peek
|
|
11
10
|
from tinybird.feedback_manager import FeedbackManager
|
|
12
|
-
from tinybird.
|
|
13
|
-
from tinybird.
|
|
11
|
+
from tinybird.tb.modules.cli import cli
|
|
12
|
+
from tinybird.tb.modules.common import coro
|
|
13
|
+
from tinybird.tb.modules.datafile import color_diff, format_datasource, format_pipe, is_file_a_datasource, peek
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@cli.command()
|
|
@@ -8,9 +8,9 @@ from click import Context
|
|
|
8
8
|
|
|
9
9
|
from tinybird.client import DoesNotExistException, TinyB
|
|
10
10
|
from tinybird.feedback_manager import FeedbackManager
|
|
11
|
-
from tinybird.
|
|
12
|
-
from tinybird.
|
|
13
|
-
from tinybird.
|
|
11
|
+
from tinybird.tb.modules.cli import cli
|
|
12
|
+
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table
|
|
13
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@cli.group()
|
|
@@ -6,7 +6,7 @@ from openai import OpenAI
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
from tinybird.client import TinyB
|
|
9
|
-
from tinybird.
|
|
9
|
+
from tinybird.tb.modules.prompts import create_project_prompt, sample_data_sql_prompt
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class DataFile(BaseModel):
|
|
@@ -6,12 +6,12 @@ import requests
|
|
|
6
6
|
|
|
7
7
|
import docker
|
|
8
8
|
from tinybird.feedback_manager import FeedbackManager
|
|
9
|
-
from tinybird.
|
|
10
|
-
from tinybird.
|
|
9
|
+
from tinybird.tb.modules.cli import cli
|
|
10
|
+
from tinybird.tb.modules.common import (
|
|
11
11
|
coro,
|
|
12
12
|
)
|
|
13
|
-
from tinybird.
|
|
14
|
-
from tinybird.
|
|
13
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
14
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
15
15
|
|
|
16
16
|
# TODO: Use the official Tinybird image once it's available 'tinybirdco/tinybird-local:latest'
|
|
17
17
|
TB_IMAGE_NAME = "registry.gitlab.com/tinybird/analytics/tinybird-local-jammy-3.11:latest"
|
|
@@ -114,7 +114,11 @@ def remove_tinybird_local(docker_client):
|
|
|
114
114
|
def get_tinybird_local_client():
|
|
115
115
|
"""Get a Tinybird client connected to the local environment."""
|
|
116
116
|
config = CLIConfig.get_project_config()
|
|
117
|
-
|
|
117
|
+
try:
|
|
118
|
+
tokens = requests.get(f"{TB_LOCAL_HOST}/tokens").json()
|
|
119
|
+
except Exception:
|
|
120
|
+
raise CLIException("Tinybird local environment is not running. Please start it with `tb local start`.")
|
|
121
|
+
|
|
118
122
|
token = tokens["workspace_admin_token"]
|
|
119
123
|
config.set_token(token)
|
|
120
124
|
config.set_host(TB_LOCAL_HOST)
|
|
@@ -4,11 +4,11 @@ from pathlib import Path
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
6
|
from tinybird.feedback_manager import FeedbackManager
|
|
7
|
-
from tinybird.
|
|
8
|
-
from tinybird.
|
|
9
|
-
from tinybird.
|
|
10
|
-
from tinybird.
|
|
11
|
-
from tinybird.
|
|
7
|
+
from tinybird.tb.modules.cli import cli
|
|
8
|
+
from tinybird.tb.modules.common import CLIException, coro
|
|
9
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
10
|
+
from tinybird.tb.modules.llm import LLM
|
|
11
|
+
from tinybird.tb.modules.local import get_tinybird_local_client
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@cli.command()
|
|
@@ -16,12 +16,12 @@ from click import Context
|
|
|
16
16
|
import tinybird.context as context
|
|
17
17
|
from tinybird.client import AuthNoTokenException, DoesNotExistException, TinyB
|
|
18
18
|
from tinybird.config import DEFAULT_API_HOST, FeatureFlags
|
|
19
|
-
from tinybird.datafile import PipeNodeTypes, PipeTypes, folder_push, get_name_version, process_file, wait_job
|
|
20
19
|
from tinybird.feedback_manager import FeedbackManager
|
|
21
|
-
from tinybird.
|
|
22
|
-
from tinybird.
|
|
23
|
-
from tinybird.
|
|
24
|
-
from tinybird.
|
|
20
|
+
from tinybird.tb.modules.branch import warn_if_in_live
|
|
21
|
+
from tinybird.tb.modules.cli import cli
|
|
22
|
+
from tinybird.tb.modules.common import coro, create_tb_client, echo_safe_humanfriendly_tables_format_smart_table
|
|
23
|
+
from tinybird.tb.modules.datafile import PipeNodeTypes, PipeTypes, folder_push, get_name_version, process_file, wait_job
|
|
24
|
+
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@cli.group()
|