tinybird-cli 5.9.1.dev2__tar.gz → 5.10.0__tar.gz
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-5.9.1.dev2 → tinybird-cli-5.10.0}/PKG-INFO +5 -3
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/__cli__.py +2 -2
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/ch_utils/engine.py +21 -1
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/client.py +5 -3
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/datafile.py +29 -20
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/feedback_manager.py +2 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/sql.py +32 -4
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/sql_template.py +26 -9
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/syncasync.py +4 -4
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/cli.py +4 -4
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/common.py +33 -10
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/connection.py +12 -1
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/datasource.py +1 -1
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tornado_template.py +1 -1
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/PKG-INFO +5 -3
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/setup.cfg +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/check_pypi.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/config.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/connectors.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/context.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/datatypes.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/git_settings.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/sql_toolset.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/requires.txt +0 -0
- {tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.10.0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,11 +18,13 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
-
5.
|
|
21
|
+
5.10.0
|
|
22
22
|
***********
|
|
23
23
|
|
|
24
|
-
- `Changed` Upgrade clickhouse-toolset to 0.32.dev0
|
|
25
24
|
- `Added` new "File not found" error to `tb check` when including files from missing paths.
|
|
25
|
+
- `Added` support for Kafka Data Sources with CA certificate.
|
|
26
|
+
- `Changed` Upgrade clickhouse-toolset to 0.32.dev0
|
|
27
|
+
- `Fixed` Correctly parse lambda expressions in indexes
|
|
26
28
|
|
|
27
29
|
5.9.0
|
|
28
30
|
***********
|
|
@@ -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__ = '5.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '5.10.0'
|
|
8
|
+
__revision__ = 'f8aa90b'
|
|
@@ -4,7 +4,14 @@ from collections import defaultdict
|
|
|
4
4
|
from dataclasses import asdict
|
|
5
5
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
6
6
|
|
|
7
|
-
from ..sql import
|
|
7
|
+
from ..sql import (
|
|
8
|
+
TableIndex,
|
|
9
|
+
TableProjection,
|
|
10
|
+
col_name,
|
|
11
|
+
engine_replicated_to_local,
|
|
12
|
+
parse_indexes_structure,
|
|
13
|
+
parse_table_structure,
|
|
14
|
+
)
|
|
8
15
|
|
|
9
16
|
DEFAULT_EMPTY_PARAMETERS = ["ttl", "partition_key", "sorting_key"]
|
|
10
17
|
DEFAULT_JOIN_EMPTY_PARAMETERS = ["join_strictness", "join_type", "key_columns"]
|
|
@@ -237,6 +244,10 @@ class TableDetails:
|
|
|
237
244
|
def indexes(self) -> List[TableIndex]:
|
|
238
245
|
return _parse_indexes(str(self.details.get("create_table_query", "")))
|
|
239
246
|
|
|
247
|
+
@property
|
|
248
|
+
def projections(self) -> List[TableProjection]:
|
|
249
|
+
return _parse_projections(self.details.get("create_table_query", ""))
|
|
250
|
+
|
|
240
251
|
def to_json(self, exclude: Optional[List[str]] = None, include_empty_details: bool = False):
|
|
241
252
|
# name, database are not exported since they are not part of the engine
|
|
242
253
|
d: Dict[str, Any] = {
|
|
@@ -833,3 +844,12 @@ def _parse_indexes(create_table_query_expr: str) -> List[TableIndex]:
|
|
|
833
844
|
return []
|
|
834
845
|
|
|
835
846
|
return parse_indexes_structure(indexes)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _parse_projections(create_table_query_expr: str) -> List[TableProjection]:
|
|
850
|
+
return [
|
|
851
|
+
TableProjection(name, expr)
|
|
852
|
+
for name, expr in re.findall(
|
|
853
|
+
r"PROJECTION\s+(\w+)\s*\(((?:[^()]|\((?:[^()]|\([^()]*\))*\))*)\)", create_table_query_expr
|
|
854
|
+
)
|
|
855
|
+
]
|
|
@@ -111,7 +111,7 @@ class TinyB(object):
|
|
|
111
111
|
retries: int = LIMIT_RETRIES,
|
|
112
112
|
use_token: Optional[str] = None,
|
|
113
113
|
**kwargs,
|
|
114
|
-
):
|
|
114
|
+
):
|
|
115
115
|
url = f"{self.host.strip('/')}/{endpoint.strip('/')}"
|
|
116
116
|
|
|
117
117
|
token_to_use = use_token if use_token else self.token
|
|
@@ -275,7 +275,7 @@ class TinyB(object):
|
|
|
275
275
|
bigquery_connection = (
|
|
276
276
|
await self.bigquery_connection() if connector == "bigquery" or connector is None else None
|
|
277
277
|
)
|
|
278
|
-
connectors = connectors
|
|
278
|
+
connectors = [*connectors, bigquery_connection] if bigquery_connection else connectors
|
|
279
279
|
if connector:
|
|
280
280
|
return [
|
|
281
281
|
{
|
|
@@ -865,6 +865,7 @@ class TinyB(object):
|
|
|
865
865
|
kafka_auto_offset_reset=None,
|
|
866
866
|
kafka_schema_registry_url=None,
|
|
867
867
|
kafka_sasl_mechanism="PLAIN",
|
|
868
|
+
kafka_ssl_ca_pem=None,
|
|
868
869
|
):
|
|
869
870
|
params = {
|
|
870
871
|
"service": "kafka",
|
|
@@ -880,7 +881,8 @@ class TinyB(object):
|
|
|
880
881
|
params["kafka_schema_registry_url"] = kafka_schema_registry_url
|
|
881
882
|
if kafka_auto_offset_reset:
|
|
882
883
|
params["kafka_auto_offset_reset"] = kafka_auto_offset_reset
|
|
883
|
-
|
|
884
|
+
if kafka_ssl_ca_pem:
|
|
885
|
+
params["kafka_ssl_ca_pem"] = kafka_ssl_ca_pem
|
|
884
886
|
connection_params = {key: value for key, value in params.items() if value is not None}
|
|
885
887
|
|
|
886
888
|
return await self._req(
|
|
@@ -60,7 +60,13 @@ from toposort import toposort
|
|
|
60
60
|
from tinybird.config import PROJECT_PATHS
|
|
61
61
|
from tinybird.sql_template_fmt import DEFAULT_FMT_LINE_LENGTH, format_sql_template
|
|
62
62
|
from tinybird.syncasync import sync_to_async
|
|
63
|
-
from tinybird.tb_cli_modules.common import
|
|
63
|
+
from tinybird.tb_cli_modules.common import (
|
|
64
|
+
_get_tb_client,
|
|
65
|
+
get_ca_pem_content,
|
|
66
|
+
get_current_main_workspace,
|
|
67
|
+
getenv_bool,
|
|
68
|
+
wait_job,
|
|
69
|
+
)
|
|
64
70
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
65
71
|
from tinybird.tb_cli_modules.exceptions import CLIGitReleaseException, CLIPipeException
|
|
66
72
|
|
|
@@ -72,7 +78,7 @@ from .sql_template import get_template_and_variables, get_used_tables_in_templat
|
|
|
72
78
|
from .tornado_template import UnClosedIfError
|
|
73
79
|
|
|
74
80
|
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
|
75
|
-
from git import HEAD, Diff, GitCommandError, InvalidGitRepositoryError, Repo
|
|
81
|
+
from git import HEAD, Diff, GitCommandError, InvalidGitRepositoryError, Repo
|
|
76
82
|
|
|
77
83
|
INTERNAL_TABLES: Tuple[str, ...] = (
|
|
78
84
|
"datasources_ops_log",
|
|
@@ -586,7 +592,7 @@ class Deployment:
|
|
|
586
592
|
self.cli_git_release.validate_local_for_release(self.current_release, check_outdated=check_outdated)
|
|
587
593
|
click.echo(FeedbackManager.info_deployment_detecting_changes_header())
|
|
588
594
|
commit = self.cli_git_release.get_main_branch_commit() if use_main else self.current_release["commit"]
|
|
589
|
-
diffs = self.cli_git_release.diff_datafiles(commit)
|
|
595
|
+
diffs = self.cli_git_release.diff_datafiles(commit) # type: ignore
|
|
590
596
|
click.echo(
|
|
591
597
|
FeedbackManager.info_git_release_diffs(
|
|
592
598
|
workspace=self.current_ws["name"],
|
|
@@ -960,7 +966,7 @@ def parse(
|
|
|
960
966
|
basepath: str = ".",
|
|
961
967
|
replace_includes: bool = True,
|
|
962
968
|
skip_eval: bool = False,
|
|
963
|
-
) -> Datafile:
|
|
969
|
+
) -> Datafile:
|
|
964
970
|
"""
|
|
965
971
|
Parses `s` string into a document
|
|
966
972
|
>>> d = parse("FROM SCRATCH\\nSOURCE 'https://example.com'\\n#this is a comment\\nMAINTAINER 'rambo' #this is me\\nNODE \\"test_01\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_00\\n\\n\\nNODE \\"test_02\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_01\\n WHERE a > 1\\n GROUP by a\\n")
|
|
@@ -1110,9 +1116,10 @@ def parse(
|
|
|
1110
1116
|
pass
|
|
1111
1117
|
finally:
|
|
1112
1118
|
file.seek(0)
|
|
1113
|
-
lines[lineno : lineno + 1] = [
|
|
1114
|
-
|
|
1115
|
-
|
|
1119
|
+
lines[lineno : lineno + 1] = [
|
|
1120
|
+
"",
|
|
1121
|
+
*list(StringIO(Template(file.read()).safe_substitute(attrs), newline=None)),
|
|
1122
|
+
]
|
|
1116
1123
|
except FileNotFoundError:
|
|
1117
1124
|
raise IncludeFileNotFoundException(f, lineno)
|
|
1118
1125
|
|
|
@@ -1211,6 +1218,7 @@ def parse(
|
|
|
1211
1218
|
"kafka_store_headers": assign_var("kafka_store_headers"),
|
|
1212
1219
|
"kafka_store_binary_headers": assign_var("kafka_store_binary_headers"),
|
|
1213
1220
|
"kafka_key_avro_deserialization": assign_var("kafka_key_avro_deserialization"),
|
|
1221
|
+
"kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
|
|
1214
1222
|
"import_service": assign_var("import_service"),
|
|
1215
1223
|
"import_connection_name": assign_var("import_connection_name"),
|
|
1216
1224
|
"import_schedule": assign_var("import_schedule"),
|
|
@@ -1318,7 +1326,7 @@ async def process_file(
|
|
|
1318
1326
|
workspace_map: Optional[Dict] = None,
|
|
1319
1327
|
workspace_lib_paths: Optional[List[Tuple[str, str]]] = None,
|
|
1320
1328
|
current_ws: Optional[Dict[str, Any]] = None,
|
|
1321
|
-
):
|
|
1329
|
+
):
|
|
1322
1330
|
if workspace_map is None:
|
|
1323
1331
|
workspace_map = {}
|
|
1324
1332
|
|
|
@@ -1349,6 +1357,7 @@ async def process_file(
|
|
|
1349
1357
|
"kafka_connection_name": params.get("kafka_connection_name", None),
|
|
1350
1358
|
"kafka_auto_offset_reset": params.get("kafka_auto_offset_reset", None),
|
|
1351
1359
|
"kafka_schema_registry_url": params.get("kafka_schema_registry_url", None),
|
|
1360
|
+
"kafka_ssl_ca_pem": get_ca_pem_content(params.get("kafka_ssl_ca_pem", None), filename),
|
|
1352
1361
|
}
|
|
1353
1362
|
|
|
1354
1363
|
connector = await tb_client.get_connection(**connector_params)
|
|
@@ -2118,7 +2127,7 @@ class PipeCheckerRunnerResponse:
|
|
|
2118
2127
|
|
|
2119
2128
|
|
|
2120
2129
|
class PipeCheckerRunner:
|
|
2121
|
-
checker_stream_result_class = unittest.runner._WritelnDecorator
|
|
2130
|
+
checker_stream_result_class = unittest.runner._WritelnDecorator
|
|
2122
2131
|
|
|
2123
2132
|
def __init__(self, pipe_name: str, host: str):
|
|
2124
2133
|
self.pipe_name = pipe_name
|
|
@@ -2281,7 +2290,7 @@ class PipeCheckerRunner:
|
|
|
2281
2290
|
)
|
|
2282
2291
|
|
|
2283
2292
|
result = PipeCheckerTextTestResult(
|
|
2284
|
-
self.checker_stream_result_class(sys.stdout), descriptions=True, verbosity=2, custom_output=custom_output
|
|
2293
|
+
self.checker_stream_result_class(sys.stdout), descriptions=True, verbosity=2, custom_output=custom_output # type: ignore
|
|
2285
2294
|
)
|
|
2286
2295
|
result.failfast = failfast
|
|
2287
2296
|
suite.run(result)
|
|
@@ -2868,7 +2877,7 @@ async def new_pipe(
|
|
|
2868
2877
|
config: Any = None,
|
|
2869
2878
|
fork_downstream: Optional[bool] = False,
|
|
2870
2879
|
fork: Optional[bool] = False,
|
|
2871
|
-
):
|
|
2880
|
+
):
|
|
2872
2881
|
# TODO use tb_client instead of calling the urls directly.
|
|
2873
2882
|
host = tb_client.host
|
|
2874
2883
|
token = tb_client.token
|
|
@@ -3270,10 +3279,10 @@ async def new_ds(
|
|
|
3270
3279
|
if job_url:
|
|
3271
3280
|
click.echo(FeedbackManager.success_dynamodb_initial_load(job_url=job_url))
|
|
3272
3281
|
|
|
3273
|
-
if
|
|
3282
|
+
if ds.get("tokens"):
|
|
3274
3283
|
await manage_tokens()
|
|
3275
3284
|
|
|
3276
|
-
if
|
|
3285
|
+
if ds.get("shared_with"):
|
|
3277
3286
|
if not user_token:
|
|
3278
3287
|
click.echo(FeedbackManager.info_skipping_shared_with_entry())
|
|
3279
3288
|
else:
|
|
@@ -3989,7 +3998,7 @@ async def build_graph(
|
|
|
3989
3998
|
mapped_workspaces.append(
|
|
3990
3999
|
workspace_map.get(shared_with)
|
|
3991
4000
|
if workspace_map.get(shared_with, None) is not None
|
|
3992
|
-
else shared_with
|
|
4001
|
+
else shared_with # type: ignore
|
|
3993
4002
|
)
|
|
3994
4003
|
r["shared_with"] = mapped_workspaces
|
|
3995
4004
|
|
|
@@ -4189,7 +4198,7 @@ async def folder_push(
|
|
|
4189
4198
|
use_main: bool = False,
|
|
4190
4199
|
check_outdated: bool = True,
|
|
4191
4200
|
hide_folders: bool = False,
|
|
4192
|
-
):
|
|
4201
|
+
):
|
|
4193
4202
|
workspaces: List[Dict[str, Any]] = (await tb_client.user_workspaces_and_branches()).get("workspaces", [])
|
|
4194
4203
|
current_ws: Dict[str, Any] = next(
|
|
4195
4204
|
(workspace for workspace in workspaces if config and workspace.get("id", ".") == config.get("id", "..")), {}
|
|
@@ -4754,7 +4763,7 @@ DATAFILE_INDENT = " " * 4
|
|
|
4754
4763
|
|
|
4755
4764
|
|
|
4756
4765
|
def format_schema(file_parts: List[str], node: Dict[str, Any]) -> List[str]:
|
|
4757
|
-
if
|
|
4766
|
+
if node.get("schema"):
|
|
4758
4767
|
file_parts.append("SCHEMA >")
|
|
4759
4768
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
4760
4769
|
columns = schema_to_sql_columns(node["columns"])
|
|
@@ -4766,7 +4775,7 @@ def format_schema(file_parts: List[str], node: Dict[str, Any]) -> List[str]:
|
|
|
4766
4775
|
|
|
4767
4776
|
|
|
4768
4777
|
def format_indices(file_parts: List[str], node: Dict[str, Any]) -> List[str]:
|
|
4769
|
-
if
|
|
4778
|
+
if node.get("indexes"):
|
|
4770
4779
|
indexes = node["indexes"]
|
|
4771
4780
|
file_parts.append("INDEXES >")
|
|
4772
4781
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
@@ -4995,7 +5004,7 @@ async def format_node_type(file_parts: List[str], node: Dict[str, Any]) -> List[
|
|
|
4995
5004
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
4996
5005
|
file_parts.append(f'COPY_MODE {node.get("mode")}')
|
|
4997
5006
|
|
|
4998
|
-
if
|
|
5007
|
+
if node.get(CopyParameters.COPY_SCHEDULE):
|
|
4999
5008
|
is_ondemand = node[CopyParameters.COPY_SCHEDULE].lower() == ON_DEMAND
|
|
5000
5009
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
5001
5010
|
file_parts.append(
|
|
@@ -5154,7 +5163,7 @@ async def folder_pull(
|
|
|
5154
5163
|
verbose: bool = True,
|
|
5155
5164
|
progress_bar: bool = False,
|
|
5156
5165
|
fmt: bool = False,
|
|
5157
|
-
):
|
|
5166
|
+
):
|
|
5158
5167
|
pattern = re.compile(match) if match else None
|
|
5159
5168
|
|
|
5160
5169
|
def _get_latest_versions(resources: List[str]):
|
|
@@ -5321,7 +5330,7 @@ async def diff_command(
|
|
|
5321
5330
|
|
|
5322
5331
|
if filenames:
|
|
5323
5332
|
if len(filenames) == 1:
|
|
5324
|
-
filenames = [filenames[0]
|
|
5333
|
+
filenames = [filenames[0], *get_project_filenames(filenames[0])]
|
|
5325
5334
|
await folder_pull(client, target_dir, False, None, True, verbose=False)
|
|
5326
5335
|
else:
|
|
5327
5336
|
filenames = get_project_filenames(".")
|
|
@@ -238,6 +238,8 @@ class FeedbackManager:
|
|
|
238
238
|
error_connection_does_not_exists = error_message("Connection {connection_id} does not exist")
|
|
239
239
|
error_connection_create = error_message("Connection {connection_name} could not be created: {error}")
|
|
240
240
|
error_connection_integration_not_available = error_message("Connection could not be created: {error}")
|
|
241
|
+
error_connection_invalid_ca_pem = error_message("Invalid CA certificate in PEM format")
|
|
242
|
+
error_connection_ca_pem_not_found = error_message("CA certificate in PEM format not found at {ca_pem}")
|
|
241
243
|
error_workspace = error_message("Workspace {workspace} not found. use 'tb workspace ls' to list your workspaces")
|
|
242
244
|
error_deleted_include = error_message(
|
|
243
245
|
"Related include file {include_file} was deleted and it's used in {filename}. Delete or remove dependency from {filename}."
|
|
@@ -38,11 +38,37 @@ class TableIndex:
|
|
|
38
38
|
return f"CLEAR INDEX IF EXISTS {self.name}"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
@dataclass
|
|
42
|
+
class TableProjection:
|
|
43
|
+
"""Defines a CH table PROJECTION"""
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
expr: str
|
|
47
|
+
|
|
48
|
+
def to_datafile(self):
|
|
49
|
+
return f"{self.name} ({self.expr})"
|
|
50
|
+
|
|
51
|
+
def to_sql(self):
|
|
52
|
+
return f"PROJECTION {self.to_datafile()}"
|
|
53
|
+
|
|
54
|
+
def add_index_sql(self):
|
|
55
|
+
return f"ADD {self.to_sql()}"
|
|
56
|
+
|
|
57
|
+
def drop_index_sql(self):
|
|
58
|
+
return f"DROP PROJECTION IF EXISTS {self.name}"
|
|
59
|
+
|
|
60
|
+
def materialize_index_sql(self):
|
|
61
|
+
return f"MATERIALIZE PROJECTION IF EXISTS {self.name}"
|
|
62
|
+
|
|
63
|
+
def clear_index_sql(self):
|
|
64
|
+
return f"CLEAR PROJECTION IF EXISTS {self.name}"
|
|
65
|
+
|
|
66
|
+
|
|
41
67
|
def as_subquery(sql: str) -> str:
|
|
42
68
|
return f"""(\n{sql}\n)"""
|
|
43
69
|
|
|
44
70
|
|
|
45
|
-
def get_format(sql: str) -> str:
|
|
71
|
+
def get_format(sql: str) -> Optional[str]:
|
|
46
72
|
"""
|
|
47
73
|
retrieves FORMAT from CH sql
|
|
48
74
|
>>> get_format('select * from test')
|
|
@@ -211,7 +237,7 @@ def format_parse_error(
|
|
|
211
237
|
return message
|
|
212
238
|
|
|
213
239
|
|
|
214
|
-
def parse_indexes_structure(indexes: List[str]) -> List[TableIndex]:
|
|
240
|
+
def parse_indexes_structure(indexes: Optional[List[str]]) -> List[TableIndex]:
|
|
215
241
|
"""
|
|
216
242
|
>>> parse_indexes_structure(["index_name a TYPE set(100) GRANULARITY 100", "index_name_bf mapValues(d) TYPE bloom_filter(0.001) GRANULARITY 16"])
|
|
217
243
|
[TableIndex(name='index_name', expr='a', type_full='set(100)', granularity='100'), TableIndex(name='index_name_bf', expr='mapValues(d)', type_full='bloom_filter(0.001)', granularity='16')]
|
|
@@ -239,6 +265,8 @@ def parse_indexes_structure(indexes: List[str]) -> List[TableIndex]:
|
|
|
239
265
|
ValueError: invalid INDEX format. Usage: `name expr TYPE type_full GRANULARITY granularity`
|
|
240
266
|
>>> parse_indexes_structure(["my_index m['key'] TYPE ngrambf_v1(1, 1024, 1, 42) GRANULARITY 1"])
|
|
241
267
|
[TableIndex(name='my_index', expr="m['key']", type_full='ngrambf_v1(1, 1024, 1, 42)', granularity='1')]
|
|
268
|
+
>>> parse_indexes_structure(["my_index_lambda arrayMap(x -> tupleElement(x,'message'), column_name) TYPE ngrambf_v1(1, 1024, 1, 42) GRANULARITY 1"])
|
|
269
|
+
[TableIndex(name='my_index_lambda', expr="arrayMap(x -> tupleElement(x,'message'), column_name)", type_full='ngrambf_v1(1, 1024, 1, 42)', granularity='1')]
|
|
242
270
|
>>> parse_indexes_structure(["ip_range_minmax_idx (toIPv6(ip_range_start), toIPv6(ip_range_end)) TYPE minmax GRANULARITY 1"])
|
|
243
271
|
[TableIndex(name='ip_range_minmax_idx', expr='(toIPv6(ip_range_start), toIPv6(ip_range_end))', type_full='minmax', granularity='1')]
|
|
244
272
|
"""
|
|
@@ -253,7 +281,7 @@ def parse_indexes_structure(indexes: List[str]) -> List[TableIndex]:
|
|
|
253
281
|
raise ValueError("invalid INDEX format. Usage: `name expr TYPE type_full GRANULARITY granularity`")
|
|
254
282
|
|
|
255
283
|
match = re.match(
|
|
256
|
-
r"(\w+)\s+([\w\s*\[\]\*\(\),\'\"
|
|
284
|
+
r"(\w+)\s+([\w\s*\[\]\*\(\),\'\"-><.]+)\s+TYPE\s+(\w+)(?:\(([\w\s*.,]+)\))?(?:\s+GRANULARITY\s+(\d+))?",
|
|
257
285
|
index,
|
|
258
286
|
)
|
|
259
287
|
if match:
|
|
@@ -498,7 +526,7 @@ REGEX_WHITESPACE = re.compile(r"\s*")
|
|
|
498
526
|
REGEX_COMMENT = re.compile(r"\-\-[^\n\r]*[\n\r]")
|
|
499
527
|
|
|
500
528
|
|
|
501
|
-
def _parse_table_structure(schema: str) -> List[Dict[str, Any]]:
|
|
529
|
+
def _parse_table_structure(schema: str) -> List[Dict[str, Any]]:
|
|
502
530
|
# CH syntax from https://clickhouse.com/docs/en/sql-reference/statements/create/table/
|
|
503
531
|
# name1 [type1] [NULL|NOT NULL] [DEFAULT|MATERIALIZED|ALIAS expr1] [compression_codec] [TTL expr1]
|
|
504
532
|
try:
|
|
@@ -359,7 +359,7 @@ def defined(x=None):
|
|
|
359
359
|
return True
|
|
360
360
|
|
|
361
361
|
|
|
362
|
-
def array_type(types):
|
|
362
|
+
def array_type(types):
|
|
363
363
|
def _f(
|
|
364
364
|
x, _type=None, default=None, defined=True, required=None, description=None, enum=None, example=None, format=None
|
|
365
365
|
):
|
|
@@ -1370,7 +1370,7 @@ _namespace = {
|
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
1372
|
|
|
1373
|
-
reserved_vars = set(["_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error"
|
|
1373
|
+
reserved_vars = set(["_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error", *list(vars(builtins))])
|
|
1374
1374
|
for p in DEFAULT_PARAM_NAMES: # we handle these in an specific manner
|
|
1375
1375
|
reserved_vars.discard(p) # `format` is part of builtins
|
|
1376
1376
|
error_vars = ["error", "custom_error"]
|
|
@@ -1552,7 +1552,7 @@ def get_var_names(t):
|
|
|
1552
1552
|
raise SQLTemplateException(e)
|
|
1553
1553
|
|
|
1554
1554
|
|
|
1555
|
-
def get_var_data(content, node_id=None):
|
|
1555
|
+
def get_var_data(content, node_id=None):
|
|
1556
1556
|
def node_to_value(x):
|
|
1557
1557
|
if type(x) in (ast.Bytes, ast.Str):
|
|
1558
1558
|
return x.s
|
|
@@ -1619,8 +1619,10 @@ def get_var_data(content, node_id=None): # noqa: C901
|
|
|
1619
1619
|
|
|
1620
1620
|
kwargs = {}
|
|
1621
1621
|
for x in node.keywords:
|
|
1622
|
-
|
|
1623
|
-
|
|
1622
|
+
value = node_to_value(x.value)
|
|
1623
|
+
kwargs[x.arg] = value
|
|
1624
|
+
if x.arg == "default":
|
|
1625
|
+
kwargs["default"] = check_default_value(value)
|
|
1624
1626
|
if func in VALID_CUSTOM_FUNCTION_NAMES:
|
|
1625
1627
|
# Type definition here is set to 'String' because it comes from a
|
|
1626
1628
|
# `defined(variable)` expression that does not contain any type hint.
|
|
@@ -1628,11 +1630,15 @@ def get_var_data(content, node_id=None): # noqa: C901
|
|
|
1628
1630
|
# args[0] check is used to avoid adding unnamed parameters found in
|
|
1629
1631
|
# templates like: `split_to_array('')`
|
|
1630
1632
|
if len(args) > 0 and args[0] not in vars and args[0]:
|
|
1631
|
-
vars[args[0]] = {
|
|
1633
|
+
vars[args[0]] = {
|
|
1634
|
+
"type": "String",
|
|
1635
|
+
"default": None,
|
|
1636
|
+
"used_in": "function_call",
|
|
1637
|
+
}
|
|
1632
1638
|
elif func == "Array":
|
|
1633
1639
|
if "default" not in kwargs:
|
|
1634
1640
|
default = kwargs.get("default", args[2] if len(args) > 2 and args[2] else None)
|
|
1635
|
-
kwargs["default"] = default
|
|
1641
|
+
kwargs["default"] = check_default_value(default)
|
|
1636
1642
|
if len(args):
|
|
1637
1643
|
vars[args[0]] = {
|
|
1638
1644
|
"type": f"Array({args[1]})" if len(args) > 1 else "Array(String)",
|
|
@@ -1644,9 +1650,11 @@ def get_var_data(content, node_id=None): # noqa: C901
|
|
|
1644
1650
|
# if this is a cast use the function name to get the type
|
|
1645
1651
|
if "default" not in kwargs:
|
|
1646
1652
|
default = kwargs.get("default", args[1] if len(args) > 1 else None)
|
|
1647
|
-
kwargs["default"] = default
|
|
1653
|
+
kwargs["default"] = check_default_value(default)
|
|
1648
1654
|
try:
|
|
1649
1655
|
vars[args[0]] = {"type": func, **kwargs}
|
|
1656
|
+
if "default" in kwargs:
|
|
1657
|
+
kwargs["default"] = check_default_value(kwargs["default"])
|
|
1650
1658
|
except TypeError as e:
|
|
1651
1659
|
logging.exception(f"pipe parsing problem {content} (node '{node_id}'): {e}")
|
|
1652
1660
|
except Exception as e:
|
|
@@ -1665,6 +1673,13 @@ def get_var_data(content, node_id=None): # noqa: C901
|
|
|
1665
1673
|
|
|
1666
1674
|
return vars
|
|
1667
1675
|
|
|
1676
|
+
def check_default_value(value):
|
|
1677
|
+
if isinstance(value, int):
|
|
1678
|
+
MAX_SAFE_INTEGER = 9007199254740991
|
|
1679
|
+
if value > MAX_SAFE_INTEGER:
|
|
1680
|
+
return str(value)
|
|
1681
|
+
return value
|
|
1682
|
+
|
|
1668
1683
|
def parse_content(content, retries=0):
|
|
1669
1684
|
try:
|
|
1670
1685
|
parsed = ast.parse(content)
|
|
@@ -1688,7 +1703,7 @@ def get_var_data(content, node_id=None): # noqa: C901
|
|
|
1688
1703
|
return [dict(name=k, **v) for k, v in vars.items()]
|
|
1689
1704
|
|
|
1690
1705
|
|
|
1691
|
-
def get_var_names_and_types(t, node_id=None):
|
|
1706
|
+
def get_var_names_and_types(t, node_id=None):
|
|
1692
1707
|
"""
|
|
1693
1708
|
>>> get_var_names_and_types(Template("SELECT * FROM filter_value WHERE description = {{Float32(with_value, 0.0)}}"))
|
|
1694
1709
|
[{'name': 'with_value', 'type': 'Float32', 'default': 0.0}]
|
|
@@ -1744,6 +1759,8 @@ def get_var_names_and_types(t, node_id=None): # noqa: C901
|
|
|
1744
1759
|
[{'name': 'symbol_id', 'type': 'Int128', 'description': 'Symbol Id', 'required': True, 'default': 11111}, {'name': 'user_id', 'type': 'Int256', 'description': 'User Id', 'default': 3555}]
|
|
1745
1760
|
>>> get_var_names_and_types(Template("SELECT now() > {{DateTime64(timestamp, '2020-09-09 10:10:10.000')}}"))
|
|
1746
1761
|
[{'name': 'timestamp', 'type': 'DateTime64', 'default': '2020-09-09 10:10:10.000'}]
|
|
1762
|
+
>>> get_var_names_and_types(Template("SELECT * FROM filter_value WHERE symbol = {{Int64(symbol_id, 9223372036854775807)}}"))
|
|
1763
|
+
[{'name': 'symbol_id', 'type': 'Int64', 'default': '9223372036854775807'}]
|
|
1747
1764
|
"""
|
|
1748
1765
|
try:
|
|
1749
1766
|
|
|
@@ -177,7 +177,7 @@ class _WorkItem:
|
|
|
177
177
|
return
|
|
178
178
|
try:
|
|
179
179
|
result = self.fn(*self.args, **self.kwargs)
|
|
180
|
-
except BaseException as exc:
|
|
180
|
+
except BaseException as exc:
|
|
181
181
|
self.future.set_exception(exc)
|
|
182
182
|
# Break a reference cycle with the exception 'exc'
|
|
183
183
|
self = None
|
|
@@ -473,11 +473,11 @@ class AsyncToSync:
|
|
|
473
473
|
if exc_info[1]:
|
|
474
474
|
try:
|
|
475
475
|
raise exc_info[1]
|
|
476
|
-
except BaseException:
|
|
476
|
+
except BaseException:
|
|
477
477
|
result = await self.awaitable(*args, **kwargs)
|
|
478
478
|
else:
|
|
479
479
|
result = await self.awaitable(*args, **kwargs)
|
|
480
|
-
except BaseException as e:
|
|
480
|
+
except BaseException as e:
|
|
481
481
|
call_result.set_exception(e)
|
|
482
482
|
else:
|
|
483
483
|
call_result.set_result(result)
|
|
@@ -639,7 +639,7 @@ class SyncToAsync:
|
|
|
639
639
|
if exc_info[1]:
|
|
640
640
|
try:
|
|
641
641
|
raise exc_info[1]
|
|
642
|
-
except BaseException:
|
|
642
|
+
except BaseException:
|
|
643
643
|
return func(*args, **kwargs)
|
|
644
644
|
else:
|
|
645
645
|
return func(*args, **kwargs)
|
|
@@ -76,7 +76,7 @@ DEFAULT_PATTERNS: List[Tuple[str, Union[str, Callable[[str], str]]]] = [
|
|
|
76
76
|
]
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
@click.group(cls=CatchAuthExceptions, context_settings={"help_option_names": ["-h", "--help"]})
|
|
79
|
+
@click.group(cls=CatchAuthExceptions, context_settings={"help_option_names": ["-h", "--help"]})
|
|
80
80
|
@click.option(
|
|
81
81
|
"--debug/--no-debug",
|
|
82
82
|
default=False,
|
|
@@ -212,7 +212,7 @@ async def cli(
|
|
|
212
212
|
if ctx.invoked_subcommand == "auth":
|
|
213
213
|
return
|
|
214
214
|
|
|
215
|
-
from tinybird.connectors import create_connector
|
|
215
|
+
from tinybird.connectors import create_connector
|
|
216
216
|
|
|
217
217
|
if gcp_project_id and gcs_bucket and google_application_credentials and not sf_account:
|
|
218
218
|
bq_config = {
|
|
@@ -679,7 +679,7 @@ async def push(
|
|
|
679
679
|
return
|
|
680
680
|
|
|
681
681
|
|
|
682
|
-
@cli.command()
|
|
682
|
+
@cli.command()
|
|
683
683
|
@click.option(
|
|
684
684
|
"--folder", default=None, type=click.Path(exists=True, file_okay=False), help="Folder where files will be placed"
|
|
685
685
|
)
|
|
@@ -1251,7 +1251,7 @@ def __unpatch_click_output():
|
|
|
1251
1251
|
click.secho = __old_click_echo
|
|
1252
1252
|
|
|
1253
1253
|
|
|
1254
|
-
@cli.command(short_help="Learn how to include info about the CLI in your shell PROMPT")
|
|
1254
|
+
@cli.command(short_help="Learn how to include info about the CLI in your shell PROMPT")
|
|
1255
1255
|
@click.pass_context
|
|
1256
1256
|
@coro
|
|
1257
1257
|
async def prompt(_ctx: Context) -> None:
|
|
@@ -1133,17 +1133,15 @@ async def push_data(
|
|
|
1133
1133
|
appended_rows = 0
|
|
1134
1134
|
parser = None
|
|
1135
1135
|
|
|
1136
|
-
if
|
|
1136
|
+
if res.get("error"):
|
|
1137
1137
|
raise CLIException(FeedbackManager.error_exception(error=res["error"]))
|
|
1138
|
-
if
|
|
1138
|
+
if res.get("errors"):
|
|
1139
1139
|
raise CLIException(FeedbackManager.error_exception(error=res["errors"]))
|
|
1140
|
-
if
|
|
1140
|
+
if res.get("blocks"):
|
|
1141
1141
|
for block in res["blocks"]:
|
|
1142
1142
|
if "process_return" in block and block["process_return"] is not None:
|
|
1143
1143
|
process_return = block["process_return"][0]
|
|
1144
|
-
parser = (
|
|
1145
|
-
process_return["parser"] if "parser" in process_return and process_return["parser"] else parser
|
|
1146
|
-
)
|
|
1144
|
+
parser = process_return["parser"] if process_return.get("parser") else parser
|
|
1147
1145
|
if parser and parser != "clickhouse":
|
|
1148
1146
|
parser = process_return["parser"]
|
|
1149
1147
|
appended_rows += process_return["lines"]
|
|
@@ -1214,12 +1212,12 @@ def autocomplete_topics(ctx: Context, args, incomplete):
|
|
|
1214
1212
|
|
|
1215
1213
|
|
|
1216
1214
|
def validate_datasource_name(name):
|
|
1217
|
-
if not isinstance(name, str) or
|
|
1215
|
+
if not isinstance(name, str) or name == "":
|
|
1218
1216
|
raise CLIException(FeedbackManager.error_datasource_name())
|
|
1219
1217
|
|
|
1220
1218
|
|
|
1221
1219
|
def validate_connection_id(connection_id):
|
|
1222
|
-
if not isinstance(connection_id, str) or
|
|
1220
|
+
if not isinstance(connection_id, str) or connection_id == "":
|
|
1223
1221
|
raise CLIException(FeedbackManager.error_datasource_connection_id())
|
|
1224
1222
|
|
|
1225
1223
|
|
|
@@ -1235,7 +1233,7 @@ def validate_kafka_group(group):
|
|
|
1235
1233
|
|
|
1236
1234
|
def validate_kafka_auto_offset_reset(auto_offset_reset):
|
|
1237
1235
|
valid_values = {"latest", "earliest", "none"}
|
|
1238
|
-
if not
|
|
1236
|
+
if auto_offset_reset not in valid_values:
|
|
1239
1237
|
raise CLIException(FeedbackManager.error_kafka_auto_offset_reset())
|
|
1240
1238
|
|
|
1241
1239
|
|
|
@@ -1588,7 +1586,7 @@ async def try_update_config_with_remote(
|
|
|
1588
1586
|
def ask_for_admin_token_interactively(ui_host: str, default_token: Optional[str]) -> str:
|
|
1589
1587
|
return (
|
|
1590
1588
|
click.prompt(
|
|
1591
|
-
f"\nCopy the \"admin your@email\" token from {ui_host}/tokens and paste it here {
|
|
1589
|
+
f"\nCopy the \"admin your@email\" token from {ui_host}/tokens and paste it here { 'OR press enter to use the token from .tinyb file' if default_token else ''}",
|
|
1592
1590
|
hide_input=True,
|
|
1593
1591
|
show_default=False,
|
|
1594
1592
|
default=default_token,
|
|
@@ -2056,3 +2054,28 @@ async def create_aws_iamrole_connection(client: TinyB, service: str, connection_
|
|
|
2056
2054
|
"""
|
|
2057
2055
|
)
|
|
2058
2056
|
click.echo(FeedbackManager.success_connection_file_created(name=conn_file_name))
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
def get_ca_pem_content(ca_pem: Optional[str], filename: Optional[str] = None) -> Optional[str]:
|
|
2060
|
+
if not ca_pem:
|
|
2061
|
+
return None
|
|
2062
|
+
|
|
2063
|
+
def is_valid_content(text_content: str) -> bool:
|
|
2064
|
+
return text_content.startswith("-----BEGIN CERTIFICATE-----")
|
|
2065
|
+
|
|
2066
|
+
ca_pem_content = ca_pem
|
|
2067
|
+
base_path = Path(getcwd(), filename).parent if filename else Path(getcwd())
|
|
2068
|
+
ca_pem_path = Path(base_path, ca_pem)
|
|
2069
|
+
path_exists = os.path.exists(ca_pem_path)
|
|
2070
|
+
|
|
2071
|
+
if not path_exists:
|
|
2072
|
+
raise CLIConnectionException(FeedbackManager.error_connection_ca_pem_not_found(ca_pem=ca_pem))
|
|
2073
|
+
|
|
2074
|
+
if ca_pem.endswith(".pem") and path_exists:
|
|
2075
|
+
with open(ca_pem_path, "r") as f:
|
|
2076
|
+
ca_pem_content = f.read()
|
|
2077
|
+
|
|
2078
|
+
if not is_valid_content(ca_pem_content):
|
|
2079
|
+
raise CLIConnectionException(FeedbackManager.error_connection_invalid_ca_pem())
|
|
2080
|
+
|
|
2081
|
+
return ca_pem_content
|
|
@@ -21,6 +21,7 @@ from tinybird.tb_cli_modules.common import (
|
|
|
21
21
|
coro,
|
|
22
22
|
create_aws_iamrole_connection,
|
|
23
23
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
24
|
+
get_ca_pem_content,
|
|
24
25
|
validate_aws_iamrole_connection_name,
|
|
25
26
|
validate_aws_iamrole_integration,
|
|
26
27
|
validate_connection_name,
|
|
@@ -44,6 +45,7 @@ DATA_CONNECTOR_SETTINGS: Dict[DataConnectorType, List[str]] = {
|
|
|
44
45
|
"kafka_security_protocol",
|
|
45
46
|
"kafka_sasl_mechanism",
|
|
46
47
|
"kafka_schema_registry_url",
|
|
48
|
+
"kafka_ssl_ca_pem",
|
|
47
49
|
],
|
|
48
50
|
DataConnectorType.GCLOUD_SCHEDULER: ["gcscheduler_region"],
|
|
49
51
|
DataConnectorType.SNOWFLAKE: [
|
|
@@ -131,6 +133,7 @@ def connection_create(ctx: Context) -> None:
|
|
|
131
133
|
default="PLAIN",
|
|
132
134
|
help="Authentication method for connection-based protocols. Defaults to 'PLAIN'",
|
|
133
135
|
)
|
|
136
|
+
@click.option("--ssl-ca-pem", default=None, help="Path or content of the CA Certificate file in PEM format")
|
|
134
137
|
@click.pass_context
|
|
135
138
|
@coro
|
|
136
139
|
async def connection_create_kafka(
|
|
@@ -142,6 +145,7 @@ async def connection_create_kafka(
|
|
|
142
145
|
auto_offset_reset: Optional[str],
|
|
143
146
|
schema_registry_url: Optional[str],
|
|
144
147
|
sasl_mechanism: Optional[str],
|
|
148
|
+
ssl_ca_pem: Optional[str],
|
|
145
149
|
) -> None:
|
|
146
150
|
"""
|
|
147
151
|
Add a Kafka connection
|
|
@@ -174,7 +178,14 @@ async def connection_create_kafka(
|
|
|
174
178
|
client: TinyB = obj["client"]
|
|
175
179
|
|
|
176
180
|
result = await client.connection_create_kafka(
|
|
177
|
-
bootstrap_servers,
|
|
181
|
+
bootstrap_servers,
|
|
182
|
+
key,
|
|
183
|
+
secret,
|
|
184
|
+
connection_name,
|
|
185
|
+
auto_offset_reset,
|
|
186
|
+
schema_registry_url,
|
|
187
|
+
sasl_mechanism,
|
|
188
|
+
get_ca_pem_content(ssl_ca_pem),
|
|
178
189
|
)
|
|
179
190
|
|
|
180
191
|
id = result["id"]
|
|
@@ -229,7 +229,7 @@ async def datasource_replace(
|
|
|
229
229
|
|
|
230
230
|
- Replace from local file `tb datasource replace [datasource_name] /path/to/local/file --sql-condition "country='ES'"`
|
|
231
231
|
|
|
232
|
-
- Replace from connector
|
|
232
|
+
- Replace from connector `tb datasource replace [datasource_name] --connector [connector_name] --sql [the_sql_to_extract_from] --sql-condition "country='ES'"`
|
|
233
233
|
"""
|
|
234
234
|
|
|
235
235
|
if not url and not connector:
|
|
@@ -848,7 +848,7 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
848
848
|
# When there are more than 2 curlies in a row, use the
|
|
849
849
|
# innermost ones. This is useful when generating languages
|
|
850
850
|
# like latex where curlies are also meaningful
|
|
851
|
-
if curly + 2 < reader.remaining() and reader[curly + 1] == "{" and reader[curly + 2] == "{":
|
|
851
|
+
if curly + 2 < reader.remaining() and reader[curly + 1] == "{" and reader[curly + 2] == "{":
|
|
852
852
|
curly += 1
|
|
853
853
|
continue
|
|
854
854
|
break
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.10.0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,11 +18,13 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
-
5.
|
|
21
|
+
5.10.0
|
|
22
22
|
***********
|
|
23
23
|
|
|
24
|
-
- `Changed` Upgrade clickhouse-toolset to 0.32.dev0
|
|
25
24
|
- `Added` new "File not found" error to `tb check` when including files from missing paths.
|
|
25
|
+
- `Added` support for Kafka Data Sources with CA certificate.
|
|
26
|
+
- `Changed` Upgrade clickhouse-toolset to 0.32.dev0
|
|
27
|
+
- `Fixed` Correctly parse lambda expressions in indexes
|
|
26
28
|
|
|
27
29
|
5.9.0
|
|
28
30
|
***********
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
{tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-5.9.1.dev2 → tinybird-cli-5.10.0}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|