tinybird-cli 5.22.3.dev0__py3-none-any.whl → 6.0.2.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinybird/__cli__.py +2 -2
- tinybird/ch_utils/engine.py +2 -77
- tinybird/client.py +3 -141
- tinybird/config.py +0 -5
- tinybird/datafile_common.py +18 -38
- tinybird/datatypes.py +13 -0
- tinybird/feedback_manager.py +1 -41
- tinybird/sql.py +6 -5
- tinybird/sql_template.py +527 -36
- tinybird/sql_toolset.py +6 -32
- tinybird/tb_cli_modules/auth.py +1 -11
- tinybird/tb_cli_modules/cli.py +1 -83
- tinybird/tb_cli_modules/common.py +27 -153
- tinybird/tb_cli_modules/config.py +1 -3
- tinybird/tb_cli_modules/connection.py +1 -225
- tinybird/tb_cli_modules/datasource.py +11 -95
- tinybird/tornado_template.py +8 -3
- {tinybird_cli-5.22.3.dev0.dist-info → tinybird_cli-6.0.2.dev0.dist-info}/METADATA +13 -20
- tinybird_cli-6.0.2.dev0.dist-info/RECORD +44 -0
- tinybird/connectors.py +0 -422
- tinybird_cli-5.22.3.dev0.dist-info/RECORD +0 -45
- {tinybird_cli-5.22.3.dev0.dist-info → tinybird_cli-6.0.2.dev0.dist-info}/WHEEL +0 -0
- {tinybird_cli-5.22.3.dev0.dist-info → tinybird_cli-6.0.2.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird_cli-5.22.3.dev0.dist-info → tinybird_cli-6.0.2.dev0.dist-info}/top_level.txt +0 -0
tinybird/sql_toolset.py
CHANGED
|
@@ -149,12 +149,11 @@ def format_where_for_mutation_command(where_clause: str) -> str:
|
|
|
149
149
|
# Functions that take table/dictionary names as string literal arguments.
|
|
150
150
|
# Normalizing these would cause incorrect cache hits since different table names
|
|
151
151
|
# would map to the same cache key.
|
|
152
|
-
# See: https://clickhouse.com/docs/en/sql-reference/functions/
|
|
153
|
-
# https://clickhouse.com/docs/en/sql-reference/functions/ext-dict-functions
|
|
152
|
+
# See: https://clickhouse.com/docs/en/sql-reference/functions/ext-dict-functions
|
|
154
153
|
# https://clickhouse.com/docs/en/sql-reference/table-functions/cluster
|
|
155
154
|
# https://clickhouse.com/docs/en/sql-reference/table-functions/remote
|
|
156
155
|
_FUNCTIONS_WITH_TABLE_NAME_ARGS = re.compile(
|
|
157
|
-
r"\b(?:
|
|
156
|
+
r"\b(?:dictGet\w*|dictHas|dictIsIn|hasColumnInTable|remote|cluster|clusterAllReplicas)\s*\(",
|
|
158
157
|
re.IGNORECASE,
|
|
159
158
|
)
|
|
160
159
|
|
|
@@ -166,7 +165,7 @@ def _normalize_sql_for_cache(sql: str) -> str:
|
|
|
166
165
|
while preserving table/column names, so queries with the same structure share
|
|
167
166
|
cache entries.
|
|
168
167
|
|
|
169
|
-
However, some functions like
|
|
168
|
+
However, some functions like dictGet*, remote(), cluster(), and
|
|
170
169
|
clusterAllReplicas() take table/dictionary names as arguments. Normalizing these
|
|
171
170
|
would incorrectly map different tables to the same cache key, so we fall back to
|
|
172
171
|
using the original SQL for such queries.
|
|
@@ -177,8 +176,6 @@ def _normalize_sql_for_cache(sql: str) -> str:
|
|
|
177
176
|
'SELECT * FROM events WHERE id = ? AND name = ?'
|
|
178
177
|
>>> _normalize_sql_for_cache("SELECT * FROM events")
|
|
179
178
|
'SELECT * FROM events'
|
|
180
|
-
>>> _normalize_sql_for_cache("SELECT joinGet('my_table', 'col', id) FROM t")
|
|
181
|
-
"SELECT joinGet('my_table', 'col', id) FROM t"
|
|
182
179
|
>>> _normalize_sql_for_cache("SELECT dictGet('my_dict', 'value', id) FROM t")
|
|
183
180
|
"SELECT dictGet('my_dict', 'value', id) FROM t"
|
|
184
181
|
>>> _normalize_sql_for_cache("SELECT * FROM remote('host', db, table)")
|
|
@@ -200,7 +197,7 @@ def _normalize_sql_for_cache(sql: str) -> str:
|
|
|
200
197
|
|
|
201
198
|
# Cache for sql_get_used_tables using normalized SQL as key.
|
|
202
199
|
# Uses lru-dict (C extension) for a fast LRU implementation.
|
|
203
|
-
_sql_get_used_tables_cache: LRU = LRU(2**
|
|
200
|
+
_sql_get_used_tables_cache: LRU = LRU(2**13)
|
|
204
201
|
_sql_get_used_tables_cache_lock = threading.Lock()
|
|
205
202
|
_sql_get_used_tables_cache_hits = 0
|
|
206
203
|
_sql_get_used_tables_cache_misses = 0
|
|
@@ -395,38 +392,15 @@ class ReplacementsDict(dict):
|
|
|
395
392
|
if isinstance(v, tuple):
|
|
396
393
|
k, r = v
|
|
397
394
|
if callable(r):
|
|
398
|
-
r =
|
|
395
|
+
r = r(self.enabled_table_functions)
|
|
399
396
|
super().__setitem__(key, (k, r))
|
|
400
397
|
return k, r
|
|
401
398
|
if callable(v):
|
|
402
|
-
v =
|
|
399
|
+
v = v(self.enabled_table_functions)
|
|
403
400
|
super().__setitem__(key, v)
|
|
404
401
|
return v
|
|
405
402
|
|
|
406
403
|
|
|
407
|
-
def update_callable_signature(func):
|
|
408
|
-
"""
|
|
409
|
-
Utility function to provide backward compatibility for callable functions
|
|
410
|
-
that don't accept the enabled_table_functions parameter.
|
|
411
|
-
"""
|
|
412
|
-
if callable(func):
|
|
413
|
-
|
|
414
|
-
def wrapper(enabled_table_functions=None):
|
|
415
|
-
# Check if the function accepts the enabled_table_functions parameter
|
|
416
|
-
import inspect
|
|
417
|
-
|
|
418
|
-
sig = inspect.signature(func)
|
|
419
|
-
if len(sig.parameters) == 0:
|
|
420
|
-
# Old-style callable with no parameters
|
|
421
|
-
return func()
|
|
422
|
-
else:
|
|
423
|
-
# New-style callable that accepts enabled_table_functions
|
|
424
|
-
return func(enabled_table_functions)
|
|
425
|
-
|
|
426
|
-
return wrapper
|
|
427
|
-
return func
|
|
428
|
-
|
|
429
|
-
|
|
430
404
|
def tables_or_sql(replacement: dict, table_functions=False, function_allow_list=None) -> set:
|
|
431
405
|
try:
|
|
432
406
|
return set(
|
tinybird/tb_cli_modules/auth.py
CHANGED
|
@@ -13,7 +13,6 @@ from tinybird.config import get_display_host
|
|
|
13
13
|
from tinybird.feedback_manager import FeedbackManager
|
|
14
14
|
from tinybird.tb_cli_modules.cli import cli
|
|
15
15
|
from tinybird.tb_cli_modules.common import (
|
|
16
|
-
configure_connector,
|
|
17
16
|
coro,
|
|
18
17
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
19
18
|
get_host_from_region,
|
|
@@ -35,11 +34,6 @@ from tinybird.tb_cli_modules.regions import Region
|
|
|
35
34
|
@click.option(
|
|
36
35
|
"--region", envvar="TB_REGION", help="Set region. Run 'tb auth ls' to show available regions. Overrides host."
|
|
37
36
|
)
|
|
38
|
-
@click.option(
|
|
39
|
-
"--connector",
|
|
40
|
-
type=click.Choice(["bigquery", "snowflake"], case_sensitive=True),
|
|
41
|
-
help="Set credentials for one of the supported connectors",
|
|
42
|
-
)
|
|
43
37
|
@click.option(
|
|
44
38
|
"-i",
|
|
45
39
|
"--interactive",
|
|
@@ -49,7 +43,7 @@ from tinybird.tb_cli_modules.regions import Region
|
|
|
49
43
|
)
|
|
50
44
|
@click.pass_context
|
|
51
45
|
@coro
|
|
52
|
-
async def auth(ctx: click.Context, token: str, host: str, region: str,
|
|
46
|
+
async def auth(ctx: click.Context, token: str, host: str, region: str, interactive: bool) -> None:
|
|
53
47
|
"""Configure auth."""
|
|
54
48
|
|
|
55
49
|
config: CLIConfig = CLIConfig.get_project_config()
|
|
@@ -58,10 +52,6 @@ async def auth(ctx: click.Context, token: str, host: str, region: str, connector
|
|
|
58
52
|
if host:
|
|
59
53
|
config.set_host(host)
|
|
60
54
|
|
|
61
|
-
if connector:
|
|
62
|
-
await configure_connector(connector)
|
|
63
|
-
return
|
|
64
|
-
|
|
65
55
|
# Only run when doing a bare 'tb auth'
|
|
66
56
|
if ctx.invoked_subcommand:
|
|
67
57
|
return
|
tinybird/tb_cli_modules/cli.py
CHANGED
|
@@ -27,7 +27,7 @@ from tinybird.client import (
|
|
|
27
27
|
OperationCanNotBePerformed,
|
|
28
28
|
TinyB,
|
|
29
29
|
)
|
|
30
|
-
from tinybird.config import CURRENT_VERSION,
|
|
30
|
+
from tinybird.config import CURRENT_VERSION, VERSION, FeatureFlags, get_config
|
|
31
31
|
from tinybird.datafile_common import (
|
|
32
32
|
AlreadyExistsException,
|
|
33
33
|
CLIGitRelease,
|
|
@@ -62,7 +62,6 @@ from tinybird.tb_cli_modules.common import (
|
|
|
62
62
|
getenv_bool,
|
|
63
63
|
is_major_semver,
|
|
64
64
|
is_post_semver,
|
|
65
|
-
load_connector_config,
|
|
66
65
|
remove_release,
|
|
67
66
|
try_update_config_with_remote,
|
|
68
67
|
)
|
|
@@ -85,32 +84,6 @@ DEFAULT_PATTERNS: List[Tuple[str, Union[str, Callable[[str], str]]]] = [
|
|
|
85
84
|
@click.option("--token", help="Use auth token, defaults to TB_TOKEN envvar, then to the .tinyb file")
|
|
86
85
|
@click.option("--host", help="Use custom host, defaults to TB_HOST envvar, then to https://api.tinybird.co")
|
|
87
86
|
@click.option("--semver", help="Semver of a Release to run the command. Example: 1.0.0", hidden=True)
|
|
88
|
-
@click.option("--gcp-project-id", help="The Google Cloud project ID", hidden=True)
|
|
89
|
-
@click.option(
|
|
90
|
-
"--gcs-bucket", help="The Google Cloud Storage bucket to write temp files when using the connectors", hidden=True
|
|
91
|
-
)
|
|
92
|
-
@click.option(
|
|
93
|
-
"--google-application-credentials",
|
|
94
|
-
envvar="GOOGLE_APPLICATION_CREDENTIALS",
|
|
95
|
-
help="Set GOOGLE_APPLICATION_CREDENTIALS",
|
|
96
|
-
hidden=True,
|
|
97
|
-
)
|
|
98
|
-
@click.option("--sf-account", help="The Snowflake Account (e.g. your-domain.west-europe.azure)", hidden=True)
|
|
99
|
-
@click.option("--sf-warehouse", help="The Snowflake warehouse name", hidden=True)
|
|
100
|
-
@click.option("--sf-database", help="The Snowflake database name", hidden=True)
|
|
101
|
-
@click.option("--sf-schema", help="The Snowflake schema name", hidden=True)
|
|
102
|
-
@click.option("--sf-role", help="The Snowflake role name", hidden=True)
|
|
103
|
-
@click.option("--sf-user", help="The Snowflake user name", hidden=True)
|
|
104
|
-
@click.option("--sf-password", help="The Snowflake password", hidden=True)
|
|
105
|
-
@click.option(
|
|
106
|
-
"--sf-storage-integration",
|
|
107
|
-
help="The Snowflake GCS storage integration name (leave empty to auto-generate one)",
|
|
108
|
-
hidden=True,
|
|
109
|
-
)
|
|
110
|
-
@click.option("--sf-stage", help="The Snowflake GCS stage name (leave empty to auto-generate one)", hidden=True)
|
|
111
|
-
@click.option(
|
|
112
|
-
"--with-headers", help="Flag to enable connector to export with headers", is_flag=True, default=False, hidden=True
|
|
113
|
-
)
|
|
114
87
|
@click.option(
|
|
115
88
|
"--version-warning/--no-version-warning",
|
|
116
89
|
envvar="TB_VERSION_WARNING",
|
|
@@ -127,19 +100,6 @@ async def cli(
|
|
|
127
100
|
token: str,
|
|
128
101
|
host: str,
|
|
129
102
|
semver: str,
|
|
130
|
-
gcp_project_id: str,
|
|
131
|
-
gcs_bucket: str,
|
|
132
|
-
google_application_credentials: str,
|
|
133
|
-
sf_account: str,
|
|
134
|
-
sf_warehouse: str,
|
|
135
|
-
sf_database: str,
|
|
136
|
-
sf_schema: str,
|
|
137
|
-
sf_role: str,
|
|
138
|
-
sf_user: str,
|
|
139
|
-
sf_password: str,
|
|
140
|
-
sf_storage_integration: str,
|
|
141
|
-
sf_stage,
|
|
142
|
-
with_headers: bool,
|
|
143
103
|
version_warning: bool,
|
|
144
104
|
show_tokens: bool,
|
|
145
105
|
) -> None:
|
|
@@ -212,52 +172,10 @@ async def cli(
|
|
|
212
172
|
if ctx.invoked_subcommand == "auth":
|
|
213
173
|
return
|
|
214
174
|
|
|
215
|
-
from tinybird.connectors import create_connector
|
|
216
|
-
|
|
217
|
-
if gcp_project_id and gcs_bucket and google_application_credentials and not sf_account:
|
|
218
|
-
bq_config = {
|
|
219
|
-
"project_id": gcp_project_id,
|
|
220
|
-
"bucket_name": gcs_bucket,
|
|
221
|
-
"service_account": google_application_credentials,
|
|
222
|
-
"with_headers": with_headers,
|
|
223
|
-
}
|
|
224
|
-
ctx.ensure_object(dict)["bigquery"] = create_connector("bigquery", bq_config)
|
|
225
|
-
if (
|
|
226
|
-
sf_account
|
|
227
|
-
and sf_warehouse
|
|
228
|
-
and sf_database
|
|
229
|
-
and sf_schema
|
|
230
|
-
and sf_role
|
|
231
|
-
and sf_user
|
|
232
|
-
and sf_password
|
|
233
|
-
and gcs_bucket
|
|
234
|
-
and google_application_credentials
|
|
235
|
-
and gcp_project_id
|
|
236
|
-
):
|
|
237
|
-
sf_config = {
|
|
238
|
-
"account": sf_account,
|
|
239
|
-
"warehouse": sf_warehouse,
|
|
240
|
-
"database": sf_database,
|
|
241
|
-
"schema": sf_schema,
|
|
242
|
-
"role": sf_role,
|
|
243
|
-
"user": sf_user,
|
|
244
|
-
"password": sf_password,
|
|
245
|
-
"storage_integration": sf_storage_integration,
|
|
246
|
-
"stage": sf_stage,
|
|
247
|
-
"bucket_name": gcs_bucket,
|
|
248
|
-
"service_account": google_application_credentials,
|
|
249
|
-
"project_id": gcp_project_id,
|
|
250
|
-
"with_headers": with_headers,
|
|
251
|
-
}
|
|
252
|
-
ctx.ensure_object(dict)["snowflake"] = create_connector("snowflake", sf_config)
|
|
253
|
-
|
|
254
175
|
logging.debug("debug enabled")
|
|
255
176
|
|
|
256
177
|
ctx.ensure_object(dict)["client"] = _get_tb_client(config.get("token", None), config["host"], semver) # type: ignore[arg-type]
|
|
257
178
|
|
|
258
|
-
for connector in SUPPORTED_CONNECTORS:
|
|
259
|
-
load_connector_config(ctx, connector, debug, check_uninstalled=True)
|
|
260
|
-
|
|
261
179
|
|
|
262
180
|
@cli.command()
|
|
263
181
|
@click.option(
|
|
@@ -16,9 +16,9 @@ from contextlib import closing
|
|
|
16
16
|
from copy import deepcopy
|
|
17
17
|
from enum import Enum
|
|
18
18
|
from functools import wraps
|
|
19
|
-
from os import chmod,
|
|
19
|
+
from os import chmod, getcwd, getenv
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import
|
|
21
|
+
from typing import Any, Callable, Dict, Iterable, List, Literal, Optional, Set, Tuple, TypedDict, Union
|
|
22
22
|
from urllib.parse import urlparse
|
|
23
23
|
|
|
24
24
|
import aiofiles
|
|
@@ -35,7 +35,6 @@ from packaging.version import Version
|
|
|
35
35
|
from tinybird.client import (
|
|
36
36
|
AuthException,
|
|
37
37
|
AuthNoTokenException,
|
|
38
|
-
ConnectorNothingToLoad,
|
|
39
38
|
DoesNotExistException,
|
|
40
39
|
JobException,
|
|
41
40
|
OperationCanNotBePerformed,
|
|
@@ -46,17 +45,11 @@ from tinybird.config import (
|
|
|
46
45
|
DEFAULT_UI_HOST,
|
|
47
46
|
DEPRECATED_PROJECT_PATHS,
|
|
48
47
|
PROJECT_PATHS,
|
|
49
|
-
SUPPORTED_CONNECTORS,
|
|
50
48
|
VERSION,
|
|
51
49
|
FeatureFlags,
|
|
52
50
|
get_config,
|
|
53
51
|
get_display_host,
|
|
54
|
-
write_config,
|
|
55
52
|
)
|
|
56
|
-
|
|
57
|
-
if TYPE_CHECKING:
|
|
58
|
-
from tinybird.connectors import Connector
|
|
59
|
-
|
|
60
53
|
from tinybird.feedback_manager import FeedbackManager, warning_message
|
|
61
54
|
from tinybird.git_settings import DEFAULT_TINYENV_FILE
|
|
62
55
|
from tinybird.syncasync import async_to_sync
|
|
@@ -78,6 +71,9 @@ from tinybird.tb_cli_modules.telemetry import (
|
|
|
78
71
|
is_ci_environment,
|
|
79
72
|
)
|
|
80
73
|
|
|
74
|
+
# Pre-compiled regex pattern for name normalization
|
|
75
|
+
_PATTERN_NORMALIZE_NAME = re.compile(r"[^0-9a-zA-Z_]")
|
|
76
|
+
|
|
81
77
|
SUPPORTED_FORMATS = ["csv", "ndjson", "json", "parquet"]
|
|
82
78
|
OLDEST_ROLLBACK = "oldest_rollback"
|
|
83
79
|
MAIN_BRANCH = "main"
|
|
@@ -89,16 +85,6 @@ def obfuscate_token(value: Optional[str]) -> Optional[str]:
|
|
|
89
85
|
return f"{value[:4]}...{value[-8:]}"
|
|
90
86
|
|
|
91
87
|
|
|
92
|
-
def create_connector(connector: str, options: Dict[str, Any]):
|
|
93
|
-
# Imported here to improve startup time when the connectors aren't used
|
|
94
|
-
from tinybird.connectors import UNINSTALLED_CONNECTORS
|
|
95
|
-
from tinybird.connectors import create_connector as _create_connector
|
|
96
|
-
|
|
97
|
-
if connector in UNINSTALLED_CONNECTORS:
|
|
98
|
-
raise CLIException(FeedbackManager.error_connector_not_installed(connector=connector))
|
|
99
|
-
return _create_connector(connector, options)
|
|
100
|
-
|
|
101
|
-
|
|
102
88
|
def coro(f):
|
|
103
89
|
@wraps(f)
|
|
104
90
|
def wrapper(*args, **kwargs):
|
|
@@ -135,7 +121,7 @@ def echo_safe_humanfriendly_tables_format_smart_table(data: Iterable[Any], colum
|
|
|
135
121
|
|
|
136
122
|
|
|
137
123
|
def normalize_datasource_name(s: str) -> str:
|
|
138
|
-
s =
|
|
124
|
+
s = _PATTERN_NORMALIZE_NAME.sub("_", s)
|
|
139
125
|
if s[0] in "0123456789":
|
|
140
126
|
return "c_" + s
|
|
141
127
|
return s
|
|
@@ -269,24 +255,6 @@ variable to '1' or 'true'."""
|
|
|
269
255
|
sys.exit(exit_code)
|
|
270
256
|
|
|
271
257
|
|
|
272
|
-
def load_connector_config(ctx: Context, connector_name: str, debug: bool, check_uninstalled: bool = False):
|
|
273
|
-
config_file = Path(getcwd()) / f".tinyb_{connector_name}"
|
|
274
|
-
try:
|
|
275
|
-
if connector_name not in ctx.ensure_object(dict):
|
|
276
|
-
with open(config_file) as file:
|
|
277
|
-
config = json.loads(file.read())
|
|
278
|
-
from tinybird.connectors import UNINSTALLED_CONNECTORS
|
|
279
|
-
|
|
280
|
-
if check_uninstalled and connector_name in UNINSTALLED_CONNECTORS:
|
|
281
|
-
click.echo(FeedbackManager.warning_connector_not_installed(connector=connector_name))
|
|
282
|
-
return
|
|
283
|
-
ctx.ensure_object(dict)[connector_name] = create_connector(connector_name, config)
|
|
284
|
-
except OSError:
|
|
285
|
-
if debug:
|
|
286
|
-
click.echo(f"** {connector_name} connector not configured")
|
|
287
|
-
pass
|
|
288
|
-
|
|
289
|
-
|
|
290
258
|
def getenv_bool(key: str, default: bool) -> bool:
|
|
291
259
|
v: Optional[str] = getenv(key)
|
|
292
260
|
if v is None:
|
|
@@ -306,31 +274,26 @@ def create_tb_client(ctx: Context) -> TinyB:
|
|
|
306
274
|
return _get_tb_client(token, host, semver=semver)
|
|
307
275
|
|
|
308
276
|
|
|
309
|
-
async def _analyze(filename: str, client: TinyB, format: str
|
|
277
|
+
async def _analyze(filename: str, client: TinyB, format: str):
|
|
310
278
|
data: Optional[bytes] = None
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
meta = await client.datasource_analyze(filename)
|
|
315
|
-
else:
|
|
316
|
-
async with aiofiles.open(filename, "rb") as file:
|
|
317
|
-
# We need to read the whole file in binary for Parquet, while for the
|
|
318
|
-
# others we just read 1KiB
|
|
319
|
-
if format == "parquet":
|
|
320
|
-
data = await file.read()
|
|
321
|
-
else:
|
|
322
|
-
data = await file.read(1024 * 1024)
|
|
323
|
-
|
|
324
|
-
meta = await client.datasource_analyze_file(data)
|
|
279
|
+
parsed = urlparse(filename)
|
|
280
|
+
if parsed.scheme in ("http", "https"):
|
|
281
|
+
meta = await client.datasource_analyze(filename)
|
|
325
282
|
else:
|
|
326
|
-
|
|
283
|
+
async with aiofiles.open(filename, "rb") as file:
|
|
284
|
+
# We need to read the whole file in binary for Parquet, while for the
|
|
285
|
+
# others we just read 1KiB
|
|
286
|
+
if format == "parquet":
|
|
287
|
+
data = await file.read()
|
|
288
|
+
else:
|
|
289
|
+
data = await file.read(1024 * 1024)
|
|
290
|
+
|
|
291
|
+
meta = await client.datasource_analyze_file(data)
|
|
327
292
|
return meta, data
|
|
328
293
|
|
|
329
294
|
|
|
330
|
-
async def _generate_datafile(
|
|
331
|
-
|
|
332
|
-
):
|
|
333
|
-
meta, data = await _analyze(filename, client, format, connector=connector)
|
|
295
|
+
async def _generate_datafile(filename: str, client: TinyB, format: str, force: Optional[bool] = False):
|
|
296
|
+
meta, data = await _analyze(filename, client, format)
|
|
334
297
|
schema = meta["analysis"]["schema"]
|
|
335
298
|
schema = schema.replace(", ", ",\n ")
|
|
336
299
|
datafile = f"""DESCRIPTION >\n Generated from {filename}\n\nSCHEMA >\n {schema}"""
|
|
@@ -465,77 +428,12 @@ async def folder_init(
|
|
|
465
428
|
)
|
|
466
429
|
|
|
467
430
|
|
|
468
|
-
async def configure_connector(connector):
|
|
469
|
-
if connector not in SUPPORTED_CONNECTORS:
|
|
470
|
-
raise CLIException(FeedbackManager.error_invalid_connector(connectors=", ".join(SUPPORTED_CONNECTORS)))
|
|
471
|
-
|
|
472
|
-
file_name = f".tinyb_{connector}"
|
|
473
|
-
config_file = Path(getcwd()) / file_name
|
|
474
|
-
if connector == "bigquery":
|
|
475
|
-
project = click.prompt("BigQuery project ID")
|
|
476
|
-
service_account = click.prompt(
|
|
477
|
-
"Path to a JSON service account file with permissions to export from BigQuery, write in Storage and sign URLs (leave empty to use GOOGLE_APPLICATION_CREDENTIALS environment variable)",
|
|
478
|
-
default=environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""),
|
|
479
|
-
)
|
|
480
|
-
bucket_name = click.prompt("Name of a Google Cloud Storage bucket to store temporary exported files")
|
|
481
|
-
|
|
482
|
-
try:
|
|
483
|
-
config = {"project_id": project, "service_account": service_account, "bucket_name": bucket_name}
|
|
484
|
-
await write_config(config, file_name)
|
|
485
|
-
except Exception:
|
|
486
|
-
raise CLIException(FeedbackManager.error_file_config(config_file=config_file))
|
|
487
|
-
elif connector == "snowflake":
|
|
488
|
-
sf_account = click.prompt("Snowflake Account (e.g. your-domain.west-europe.azure)")
|
|
489
|
-
sf_warehouse = click.prompt("Snowflake warehouse name")
|
|
490
|
-
sf_database = click.prompt("Snowflake database name")
|
|
491
|
-
sf_schema = click.prompt("Snowflake schema name")
|
|
492
|
-
sf_role = click.prompt("Snowflake role name")
|
|
493
|
-
sf_user = click.prompt("Snowflake user name")
|
|
494
|
-
sf_password = click.prompt("Snowflake password")
|
|
495
|
-
sf_storage_integration = click.prompt(
|
|
496
|
-
"Snowflake GCS storage integration name (leave empty to auto-generate one)", default=""
|
|
497
|
-
)
|
|
498
|
-
sf_stage = click.prompt("Snowflake GCS stage name (leave empty to auto-generate one)", default="")
|
|
499
|
-
project = click.prompt("Google Cloud project ID to store temporary files")
|
|
500
|
-
service_account = click.prompt(
|
|
501
|
-
"Path to a JSON service account file with permissions to write in Storagem, sign URLs and IAM (leave empty to use GOOGLE_APPLICATION_CREDENTIALS environment variable)",
|
|
502
|
-
default=environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""),
|
|
503
|
-
)
|
|
504
|
-
bucket_name = click.prompt("Name of a Google Cloud Storage bucket to store temporary exported files")
|
|
505
|
-
|
|
506
|
-
if not service_account:
|
|
507
|
-
service_account = getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
508
|
-
|
|
509
|
-
try:
|
|
510
|
-
config = {
|
|
511
|
-
"account": sf_account,
|
|
512
|
-
"warehouse": sf_warehouse,
|
|
513
|
-
"database": sf_database,
|
|
514
|
-
"schema": sf_schema,
|
|
515
|
-
"role": sf_role,
|
|
516
|
-
"user": sf_user,
|
|
517
|
-
"password": sf_password,
|
|
518
|
-
"storage_integration": sf_storage_integration,
|
|
519
|
-
"stage": sf_stage,
|
|
520
|
-
"service_account": service_account,
|
|
521
|
-
"bucket_name": bucket_name,
|
|
522
|
-
"project_id": project,
|
|
523
|
-
}
|
|
524
|
-
await write_config(config, file_name)
|
|
525
|
-
except Exception:
|
|
526
|
-
raise CLIException(FeedbackManager.error_file_config(config_file=config_file))
|
|
527
|
-
|
|
528
|
-
click.echo(FeedbackManager.success_connector_config(connector=connector, file_name=file_name))
|
|
529
|
-
|
|
530
|
-
|
|
531
431
|
def _compare_region_host(region_name_or_host: str, region: Dict[str, Any]) -> bool:
|
|
532
432
|
if region["name"].lower() == region_name_or_host:
|
|
533
433
|
return True
|
|
534
434
|
if region["host"] == region_name_or_host:
|
|
535
435
|
return True
|
|
536
|
-
|
|
537
|
-
return True
|
|
538
|
-
return False
|
|
436
|
+
return region["api_host"] == region_name_or_host
|
|
539
437
|
|
|
540
438
|
|
|
541
439
|
def ask_for_region_interactively(regions):
|
|
@@ -1071,33 +969,17 @@ async def push_data(
|
|
|
1071
969
|
ctx: Context,
|
|
1072
970
|
datasource_name: str,
|
|
1073
971
|
url,
|
|
1074
|
-
connector: Optional[str],
|
|
1075
|
-
sql: Optional[str],
|
|
1076
972
|
mode: str = "append",
|
|
1077
973
|
sql_condition: Optional[str] = None,
|
|
1078
974
|
replace_options=None,
|
|
1079
|
-
ignore_empty: bool = False,
|
|
1080
975
|
concurrency: int = 1,
|
|
1081
976
|
):
|
|
1082
977
|
if url and type(url) is tuple:
|
|
1083
978
|
url = url[0]
|
|
1084
979
|
client: TinyB = ctx.obj["client"]
|
|
1085
980
|
|
|
1086
|
-
if
|
|
1087
|
-
|
|
1088
|
-
if connector not in ctx.obj:
|
|
1089
|
-
raise CLIException(FeedbackManager.error_connector_not_configured(connector=connector))
|
|
1090
|
-
else:
|
|
1091
|
-
_connector: Connector = ctx.obj[connector]
|
|
1092
|
-
click.echo(FeedbackManager.info_starting_export_process(connector=connector))
|
|
1093
|
-
try:
|
|
1094
|
-
url = _connector.export_to_gcs(sql, datasource_name, mode)
|
|
1095
|
-
except ConnectorNothingToLoad as e:
|
|
1096
|
-
if ignore_empty:
|
|
1097
|
-
click.echo(str(e))
|
|
1098
|
-
return
|
|
1099
|
-
else:
|
|
1100
|
-
raise e
|
|
981
|
+
if not url:
|
|
982
|
+
raise CLIException(FeedbackManager.error_missing_url(datasource=datasource_name))
|
|
1101
983
|
|
|
1102
984
|
def cb(res):
|
|
1103
985
|
if cb.First: # type: ignore[attr-defined]
|
|
@@ -1199,19 +1081,13 @@ async def push_data(
|
|
|
1199
1081
|
else:
|
|
1200
1082
|
click.echo(FeedbackManager.success_appended_datasource(datasource=datasource_name))
|
|
1201
1083
|
click.echo(FeedbackManager.info_data_pushed(datasource=datasource_name))
|
|
1202
|
-
finally:
|
|
1203
|
-
try:
|
|
1204
|
-
for url in urls:
|
|
1205
|
-
_connector.clean(urlparse(url).path.split("/")[-1])
|
|
1206
|
-
except Exception:
|
|
1207
|
-
pass
|
|
1208
1084
|
|
|
1209
1085
|
|
|
1210
1086
|
async def sync_data(ctx, datasource_name: str, yes: bool):
|
|
1211
1087
|
client: TinyB = ctx.obj["client"]
|
|
1212
1088
|
datasource = await client.get_datasource(datasource_name)
|
|
1213
1089
|
|
|
1214
|
-
VALID_DATASOURCES = ["
|
|
1090
|
+
VALID_DATASOURCES = ["s3", "s3_iamrole", "gcs"]
|
|
1215
1091
|
if datasource["type"] not in VALID_DATASOURCES:
|
|
1216
1092
|
raise CLIException(FeedbackManager.error_sync_not_supported(valid_datasources=VALID_DATASOURCES))
|
|
1217
1093
|
|
|
@@ -1296,7 +1172,7 @@ def validate_kafka_bootstrap_servers(bootstrap_servers):
|
|
|
1296
1172
|
try:
|
|
1297
1173
|
sock.settimeout(3)
|
|
1298
1174
|
sock.connect((host, port))
|
|
1299
|
-
except
|
|
1175
|
+
except TimeoutError:
|
|
1300
1176
|
raise CLIException(FeedbackManager.error_kafka_bootstrap_server_conn_timeout())
|
|
1301
1177
|
except Exception:
|
|
1302
1178
|
raise CLIException(FeedbackManager.error_kafka_bootstrap_server_conn())
|
|
@@ -2022,7 +1898,7 @@ async def validate_aws_iamrole_connection_name(
|
|
|
2022
1898
|
client: TinyB, connection_name: Optional[str], no_validate: Optional[bool] = False
|
|
2023
1899
|
) -> str:
|
|
2024
1900
|
if connection_name and no_validate is False:
|
|
2025
|
-
if await client.get_connector(connection_name
|
|
1901
|
+
if await client.get_connector(connection_name) is not None:
|
|
2026
1902
|
raise CLIConnectionException(FeedbackManager.info_connection_already_exists(name=connection_name))
|
|
2027
1903
|
else:
|
|
2028
1904
|
while not connection_name:
|
|
@@ -2039,8 +1915,6 @@ async def validate_aws_iamrole_connection_name(
|
|
|
2039
1915
|
class DataConnectorType(str, Enum):
|
|
2040
1916
|
KAFKA = "kafka"
|
|
2041
1917
|
GCLOUD_SCHEDULER = "gcscheduler"
|
|
2042
|
-
SNOWFLAKE = "snowflake"
|
|
2043
|
-
BIGQUERY = "bigquery"
|
|
2044
1918
|
GCLOUD_STORAGE = "gcs"
|
|
2045
1919
|
GCLOUD_STORAGE_HMAC = "gcs_hmac"
|
|
2046
1920
|
GCLOUD_STORAGE_SA = "gcs_service_account"
|
|
@@ -31,9 +31,7 @@ class FeatureFlags:
|
|
|
31
31
|
def send_telemetry(cls) -> bool:
|
|
32
32
|
if os.environ.get("TB_CLI_TELEMETRY_OPTOUT", "0") == "1":
|
|
33
33
|
return False
|
|
34
|
-
|
|
35
|
-
return False
|
|
36
|
-
return True
|
|
34
|
+
return not ("x.y.z" in CURRENT_VERSION and os.environ.get("TB_CLI_TELEMETRY_SEND_IN_LOCAL", "0") == "0")
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
def compare_versions(a: str, b: str) -> int:
|