tinybird-cli 4.0.2.dev0__tar.gz → 4.1.1__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-4.0.2.dev0 → tinybird-cli-4.1.1}/PKG-INFO +15 -4
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/__cli__.py +2 -2
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/client.py +17 -2
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/context.py +1 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/datafile.py +5 -5
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/feedback_manager.py +10 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/sql_template.py +92 -66
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/cicd.py +1 -1
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/common.py +2 -1
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/datasource.py +4 -2
- tinybird-cli-4.1.1/tinybird/tb_cli_modules/token.py +335 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tornado_template.py +9 -1
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird_cli.egg-info/PKG-INFO +15 -4
- tinybird-cli-4.0.2.dev0/tinybird/tb_cli_modules/token.py +0 -127
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/setup.cfg +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/check_pypi.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/config.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/connectors.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/datatypes.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/git_settings.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/sql.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/sql_toolset.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/syncasync.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird_cli.egg-info/requires.txt +0 -0
- {tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/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: 4.
|
|
3
|
+
Version: 4.1.1
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,15 +18,26 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
+
4.1.1
|
|
22
|
+
************
|
|
23
|
+
|
|
24
|
+
- `Fixed` workspace info gathering when switching branches. We were prioritizing the general workspace properties over the user ones.
|
|
25
|
+
|
|
26
|
+
4.1.0
|
|
27
|
+
************
|
|
28
|
+
|
|
29
|
+
- `Added` `tb token create` command to be able to create static and JWT tokens from the CLI. You can check more information at https://www.tinybird.co/blog-posts/jwt-api-endpoints-public-beta
|
|
30
|
+
- `Fixed` `tb init --git` to pin `tinybird-cli>=4,<5` in `requirements.txt` to avoid issues with the latest version of the CLI.
|
|
31
|
+
|
|
21
32
|
4.0.0
|
|
22
33
|
************
|
|
23
34
|
|
|
24
|
-
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to
|
|
35
|
+
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to this version.
|
|
25
36
|
|
|
26
37
|
- `Deprecated` `--semver` flag and `tb release` commands are now deprecated. You can keep using `tb deploy` to integrate and deploy from git. Changes are deployed to the main Workspace instead of to a Release.
|
|
27
|
-
- `Removed` `--cicd` flag and CI/CD templates generation from `tb init`. You can still use the git integration, just create your own pipelines. You can use the ones in this repo as an
|
|
38
|
+
- `Removed` `--cicd` flag and CI/CD templates generation from `tb init`. You can still use the git integration, just create your own pipelines. You can use the ones in this repo as an example https://github.com/tinybirdco/ci
|
|
28
39
|
- `Removed` `tb env` command is removed, use `tb branch` instead.
|
|
29
|
-
- `Deprecated` .datasource files with `ENGINE "Join"`
|
|
40
|
+
- `Deprecated` .datasource files with `ENGINE "Join"` is deprecated, use `Engine "MergeeTree"` instead.
|
|
30
41
|
- `Deprecated` `tb materialize`
|
|
31
42
|
- `Removed` Drop the `--timeout` flag from `tb push` which made the populate job to timeout. You can use now `--wait` to wait for the job to finish or nothing to just create the job and return.
|
|
32
43
|
- `Removed` Support for `KEY` directive is removed. The `KEY` was used to create a Data Source with Join engine by the given `KEY` column name. Join engines are also deprecated, you can use a regular `MergeTree` Data Source instead and adapt the pipes SQL accordingly.
|
|
@@ -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__ = '4.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '4.1.1'
|
|
8
|
+
__revision__ = 'b6a6857'
|
|
@@ -209,11 +209,21 @@ class TinyB(object):
|
|
|
209
209
|
return None
|
|
210
210
|
|
|
211
211
|
async def create_token(
|
|
212
|
-
self, name: str, scope: str, origin_code: Optional[str], origin_resource_name_or_id: Optional[str]
|
|
212
|
+
self, name: str, scope: List[str], origin_code: Optional[str], origin_resource_name_or_id: Optional[str] = None
|
|
213
213
|
):
|
|
214
214
|
origin = origin_code or "C" # == Origins.CUSTOM if none specified
|
|
215
|
+
params = {
|
|
216
|
+
"name": name,
|
|
217
|
+
"origin": origin,
|
|
218
|
+
}
|
|
219
|
+
if origin_resource_name_or_id:
|
|
220
|
+
params["resource_id"] = origin_resource_name_or_id
|
|
221
|
+
|
|
222
|
+
# TODO: We should support sending multiple scopes in the body of the request
|
|
223
|
+
url = f"/v0/tokens?{urlencode(params)}"
|
|
224
|
+
url = url + "&" + "&".join([f"scope={scope}" for scope in scope])
|
|
215
225
|
return await self._req(
|
|
216
|
-
|
|
226
|
+
url,
|
|
217
227
|
method="POST",
|
|
218
228
|
data="",
|
|
219
229
|
)
|
|
@@ -1163,6 +1173,11 @@ class TinyB(object):
|
|
|
1163
1173
|
params = self._token_to_params(token)
|
|
1164
1174
|
return await self._req(f"/v0/tokens?{params}", method="POST", data="")
|
|
1165
1175
|
|
|
1176
|
+
async def create_jwt_token(self, name: str, expiration_time: int, scopes: List[Dict[str, Any]]):
|
|
1177
|
+
url_params = {"name": name, "expiration_time": expiration_time}
|
|
1178
|
+
body = json.dumps({"scopes": scopes})
|
|
1179
|
+
return await self._req(f"/v0/tokens?{urlencode(url_params)}", method="POST", data=body)
|
|
1180
|
+
|
|
1166
1181
|
async def token_update(self, token: Dict[str, Any]):
|
|
1167
1182
|
name = token["name"]
|
|
1168
1183
|
params = self._token_to_params(token)
|
|
@@ -7,6 +7,7 @@ hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
|
|
|
7
7
|
hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
|
|
8
8
|
use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
|
|
9
9
|
allow_gatherer_fallback: ContextVar[bool] = ContextVar("allow_gatherer_fallback")
|
|
10
|
+
gatherer_allow_s3_backup_on_user_errors: ContextVar[bool] = ContextVar("gatherer_allow_s3_backup_on_user_errors")
|
|
10
11
|
disable_template_security_validation: ContextVar[bool] = ContextVar("disable_template_security_validation")
|
|
11
12
|
origin: ContextVar[str] = ContextVar("origin")
|
|
12
13
|
request_id: ContextVar[str] = ContextVar("request_id")
|
|
@@ -773,7 +773,7 @@ def parse_pipe(
|
|
|
773
773
|
for node in doc.nodes:
|
|
774
774
|
sql = node.get("sql", "")
|
|
775
775
|
if sql.strip()[0] == "%":
|
|
776
|
-
sql, _ = render_sql_template(sql[1:], test_mode=True, name=node["name"])
|
|
776
|
+
sql, _, _ = render_sql_template(sql[1:], test_mode=True, name=node["name"])
|
|
777
777
|
# it'll fail with a ModuleNotFoundError when the toolset is not available but it returns the parsed doc
|
|
778
778
|
from tinybird.sql_toolset import format_sql as toolset_format_sql
|
|
779
779
|
|
|
@@ -785,7 +785,7 @@ def parse_pipe(
|
|
|
785
785
|
)
|
|
786
786
|
)
|
|
787
787
|
except ValueError as e:
|
|
788
|
-
t, template_variables = get_template_and_variables(sql, name=node["name"])
|
|
788
|
+
t, template_variables, _ = get_template_and_variables(sql, name=node["name"])
|
|
789
789
|
|
|
790
790
|
if sql.strip()[0] != "%" and len(template_variables) > 0:
|
|
791
791
|
raise click.ClickException(FeedbackManager.error_template_start(filename=filename))
|
|
@@ -1492,7 +1492,7 @@ async def process_file(
|
|
|
1492
1492
|
is_template = False
|
|
1493
1493
|
if sql[0] == "%":
|
|
1494
1494
|
try:
|
|
1495
|
-
sql_rendered, _ = render_sql_template(sql[1:], test_mode=True)
|
|
1495
|
+
sql_rendered, _, _ = render_sql_template(sql[1:], test_mode=True)
|
|
1496
1496
|
except Exception as e:
|
|
1497
1497
|
raise click.ClickException(
|
|
1498
1498
|
FeedbackManager.error_parsing_node(node=node["name"], pipe=name, error=str(e))
|
|
@@ -2785,7 +2785,7 @@ async def new_pipe(
|
|
|
2785
2785
|
click.echo(FeedbackManager.info_create_not_found_token(token=token_name))
|
|
2786
2786
|
try:
|
|
2787
2787
|
r = await tb_client.create_token(
|
|
2788
|
-
token_name, f"PIPES:{tk['permissions']}:{p['name']}", "P", p["name"]
|
|
2788
|
+
token_name, [f"PIPES:{tk['permissions']}:{p['name']}"], "P", p["name"]
|
|
2789
2789
|
)
|
|
2790
2790
|
token = r["token"] # type: ignore
|
|
2791
2791
|
except Exception as e:
|
|
@@ -2896,7 +2896,7 @@ async def new_ds(
|
|
|
2896
2896
|
token_name = tk["token_name"]
|
|
2897
2897
|
click.echo(FeedbackManager.info_create_not_found_token(token=token_name))
|
|
2898
2898
|
# DS == token_origin.Origins.DATASOURCE
|
|
2899
|
-
await client.create_token(token_name, f"DATASOURCES:{tk['permissions']}:{ds_name}", "DS", ds_name)
|
|
2899
|
+
await client.create_token(token_name, [f"DATASOURCES:{tk['permissions']}:{ds_name}"], "DS", ds_name)
|
|
2900
2900
|
else:
|
|
2901
2901
|
click.echo(FeedbackManager.info_create_found_token(token=token_name))
|
|
2902
2902
|
scopes = [f"DATASOURCES:{tk['permissions']}:{ds_name}"]
|
|
@@ -348,6 +348,13 @@ class FeedbackManager:
|
|
|
348
348
|
"{connector} Data sources require a post-release deployment. Increment the post-release number of the semver (for example: 0.0.1 -> 0.0.1-1) to do so. You can read more about post-releases at https://www.tinybird.co/docs/production/deployment-strategies"
|
|
349
349
|
)
|
|
350
350
|
|
|
351
|
+
error_number_of_scopes_and_resources_mismatch = error_message(
|
|
352
|
+
"The number of --scope and --resource options must be the same"
|
|
353
|
+
)
|
|
354
|
+
error_number_of_fixed_params_and_resources_mismatch = error_message(
|
|
355
|
+
"The number of --fixed-params options must not exceed the number of --scope and --resource options."
|
|
356
|
+
)
|
|
357
|
+
|
|
351
358
|
info_incl_relative_path = info_message("** Relative path {path} does not exist, skipping.")
|
|
352
359
|
info_ignoring_incl_file = info_message(
|
|
353
360
|
"** Ignoring file {filename}. .incl files are not checked independently. They are checked as part of the file that includes them. Please check the file that includes this .incl file."
|
|
@@ -422,6 +429,9 @@ Ready? """
|
|
|
422
429
|
"You are going to manually update workspace commit reference manually, this is just for special occasions. Do you want to update current commit reference '{current_commit}' to '{new_commit}'?"
|
|
423
430
|
)
|
|
424
431
|
|
|
432
|
+
warning_exchange = warning_message(
|
|
433
|
+
"Warning: Do you want to exchange Data Source {datasource_a} by Data Source {datasource_b}?"
|
|
434
|
+
)
|
|
425
435
|
warning_no_test_results = warning_message("Warning: No test results to show")
|
|
426
436
|
warning_using_branch_token = warning_message("** You're using the token defined in $TB_TOKEN.")
|
|
427
437
|
warning_using_branch_host = warning_message("** You're using the token defined in $TB_HOST.")
|
|
@@ -42,6 +42,24 @@ class SQLTemplateException(ValueError):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
DEFAULT_PARAM_NAMES = ["format", "q"]
|
|
45
|
+
RESERVED_PARAM_NAMES = [
|
|
46
|
+
"__tb__semver",
|
|
47
|
+
"debug_source_tables",
|
|
48
|
+
"debug",
|
|
49
|
+
"explain",
|
|
50
|
+
"finalize_aggregations",
|
|
51
|
+
"output_format_json_quote_64bit_integers",
|
|
52
|
+
"output_format_json_quote_denormals",
|
|
53
|
+
"output_format_parquet_string_as_string",
|
|
54
|
+
"pipeline",
|
|
55
|
+
"playground",
|
|
56
|
+
"q",
|
|
57
|
+
"query_id",
|
|
58
|
+
"release_replacements",
|
|
59
|
+
"tag",
|
|
60
|
+
"template_parameters",
|
|
61
|
+
"token",
|
|
62
|
+
]
|
|
45
63
|
|
|
46
64
|
parameter_types = [
|
|
47
65
|
"String",
|
|
@@ -1771,6 +1789,8 @@ def get_template_and_variables(sql: str, name: Optional[str], escape_arrays: boo
|
|
|
1771
1789
|
it is important to NOT MODIFY THESE OBJECTS.
|
|
1772
1790
|
Neither render_sql_template() or generate() modify them, so neither should you
|
|
1773
1791
|
"""
|
|
1792
|
+
variable_warnings = []
|
|
1793
|
+
|
|
1774
1794
|
try:
|
|
1775
1795
|
t = Template(sql, name)
|
|
1776
1796
|
template_variables = get_var_names(t)
|
|
@@ -1780,10 +1800,12 @@ def get_template_and_variables(sql: str, name: Optional[str], escape_arrays: boo
|
|
|
1780
1800
|
name = variable["name"]
|
|
1781
1801
|
line = variable["line"]
|
|
1782
1802
|
raise ValueError(f'"{name}" can not be used as a variable name, line {line}')
|
|
1803
|
+
if variable["name"] in RESERVED_PARAM_NAMES:
|
|
1804
|
+
variable_warnings.append(variable["name"])
|
|
1783
1805
|
|
|
1784
1806
|
wrap_vars(t, escape_arrays=escape_arrays)
|
|
1785
1807
|
|
|
1786
|
-
return t, template_variables
|
|
1808
|
+
return t, template_variables, variable_warnings
|
|
1787
1809
|
except SecurityException as e:
|
|
1788
1810
|
raise SQLTemplateException(e)
|
|
1789
1811
|
|
|
@@ -1836,96 +1858,96 @@ def render_sql_template(
|
|
|
1836
1858
|
variables: Optional[dict] = None,
|
|
1837
1859
|
test_mode: bool = False,
|
|
1838
1860
|
name: Optional[str] = None,
|
|
1839
|
-
) -> Tuple[str, dict]:
|
|
1861
|
+
) -> Tuple[str, dict, list]:
|
|
1840
1862
|
"""
|
|
1841
1863
|
>>> render_sql_template("select * from table where f = {{Float32(foo)}}", { 'foo': -1 })
|
|
1842
|
-
("select * from table where f = toFloat32('-1.0')", {})
|
|
1864
|
+
("select * from table where f = toFloat32('-1.0')", {}, [])
|
|
1843
1865
|
>>> render_sql_template("{% if defined(open) %}ERROR{% else %}YEAH!{% end %}")
|
|
1844
|
-
('YEAH!', {})
|
|
1866
|
+
('YEAH!', {}, [])
|
|
1845
1867
|
>>> render_sql_template("{% if defined(close) %}ERROR{% else %}YEAH!{% end %}")
|
|
1846
|
-
('YEAH!', {})
|
|
1868
|
+
('YEAH!', {}, [])
|
|
1847
1869
|
>>> render_sql_template("{% if defined(input) %}ERROR{% else %}YEAH!{% end %}")
|
|
1848
|
-
('YEAH!', {})
|
|
1870
|
+
('YEAH!', {}, [])
|
|
1849
1871
|
>>> render_sql_template("{% if defined(print) %}ERROR{% else %}YEAH!{% end %}")
|
|
1850
|
-
('YEAH!', {})
|
|
1872
|
+
('YEAH!', {}, [])
|
|
1851
1873
|
>>> render_sql_template("select * from table where str = {{foo}}", { 'foo': 'test' })
|
|
1852
|
-
("select * from table where str = 'test'", {})
|
|
1874
|
+
("select * from table where str = 'test'", {}, [])
|
|
1853
1875
|
>>> render_sql_template("select * from table where f = {{foo}}", { 'foo': 1.0 })
|
|
1854
|
-
('select * from table where f = 1.0', {})
|
|
1876
|
+
('select * from table where f = 1.0', {}, [])
|
|
1855
1877
|
>>> render_sql_template("select {{Boolean(foo)}} from table", { 'foo': True })
|
|
1856
|
-
('select 1 from table', {})
|
|
1878
|
+
('select 1 from table', {}, [])
|
|
1857
1879
|
>>> render_sql_template("select {{Boolean(foo)}} from table", { 'foo': False })
|
|
1858
|
-
('select 0 from table', {})
|
|
1880
|
+
('select 0 from table', {}, [])
|
|
1859
1881
|
>>> render_sql_template("select * from table where f = {{Float32(foo)}}", { 'foo': 1 })
|
|
1860
|
-
("select * from table where f = toFloat32('1.0')", {})
|
|
1882
|
+
("select * from table where f = toFloat32('1.0')", {}, [])
|
|
1861
1883
|
>>> render_sql_template("select * from table where f = {{foo}}", { 'foo': "';drop table users;" })
|
|
1862
|
-
("select * from table where f = '\\\\';drop table users;'", {})
|
|
1884
|
+
("select * from table where f = '\\\\';drop table users;'", {}, [])
|
|
1863
1885
|
>>> render_sql_template("select * from {{symbol(foo)}}", { 'foo': 'table-name' })
|
|
1864
|
-
('select * from `table-name`', {})
|
|
1886
|
+
('select * from `table-name`', {}, [])
|
|
1865
1887
|
>>> render_sql_template("select * from {{symbol(foo)}}", { 'foo': '"table-name"' })
|
|
1866
|
-
('select * from `table-name`', {})
|
|
1888
|
+
('select * from `table-name`', {}, [])
|
|
1867
1889
|
>>> render_sql_template("select * from {{table(foo)}}", { 'foo': '"table-name"' })
|
|
1868
|
-
('select * from table-name', {})
|
|
1890
|
+
('select * from table-name', {}, [])
|
|
1869
1891
|
>>> render_sql_template("select * from {{Int32(foo)}}", { 'foo': 'non_int' })
|
|
1870
1892
|
Traceback (most recent call last):
|
|
1871
1893
|
...
|
|
1872
1894
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error validating 'non_int' to type Int32
|
|
1873
1895
|
>>> render_sql_template("select * from table where f = {{Float32(foo)}}", test_mode=True)
|
|
1874
|
-
("select * from table where f = toFloat32('0.0')", {})
|
|
1896
|
+
("select * from table where f = toFloat32('0.0')", {}, [])
|
|
1875
1897
|
>>> render_sql_template("SELECT * FROM query_log__dev where a = {{test}}", test_mode=True)
|
|
1876
|
-
("SELECT * FROM query_log__dev where a = '__placeholder__'", {})
|
|
1898
|
+
("SELECT * FROM query_log__dev where a = '__placeholder__'", {}, [])
|
|
1877
1899
|
>>> render_sql_template("SELECT {{test}}", {'token':'testing'})
|
|
1878
1900
|
Traceback (most recent call last):
|
|
1879
1901
|
...
|
|
1880
1902
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: expression "test" evaluated to null
|
|
1881
1903
|
>>> render_sql_template('{% if test %}SELECT 1{% else %} select 2 {% end %}')
|
|
1882
|
-
(' select 2 ', {})
|
|
1904
|
+
(' select 2 ', {}, [])
|
|
1883
1905
|
>>> render_sql_template('{% if Int32(test, 1) %}SELECT 1{% else %} select 2 {% end %}')
|
|
1884
|
-
('SELECT 1', {})
|
|
1906
|
+
('SELECT 1', {}, [])
|
|
1885
1907
|
>>> render_sql_template('{% for v in test %}SELECT {{v}} {% end %}',test_mode=True)
|
|
1886
|
-
("SELECT '__placeholder__' SELECT '__placeholder__' SELECT '__placeholder__' ", {})
|
|
1908
|
+
("SELECT '__placeholder__' SELECT '__placeholder__' SELECT '__placeholder__' ", {}, [])
|
|
1887
1909
|
>>> render_sql_template("select {{Int32(foo, 1)}}", test_mode=True)
|
|
1888
|
-
("select toInt32('1')", {})
|
|
1910
|
+
("select toInt32('1')", {}, [])
|
|
1889
1911
|
>>> render_sql_template("SELECT count() c FROM test_table where a > {{Float32(myvar)}} {% if defined(my_condition) %} and c = Int32({{my_condition}}){% end %}", {'myvar': 1.0})
|
|
1890
|
-
("SELECT count() c FROM test_table where a > toFloat32('1.0') ", {})
|
|
1912
|
+
("SELECT count() c FROM test_table where a > toFloat32('1.0') ", {}, [])
|
|
1891
1913
|
>>> render_sql_template("SELECT count() c FROM where {{sql_and(a=a, b=b)}}", {'a': '1', 'b': '2'})
|
|
1892
|
-
("SELECT count() c FROM where a = '1' and b = '2'", {})
|
|
1914
|
+
("SELECT count() c FROM where a = '1' and b = '2'", {}, [])
|
|
1893
1915
|
>>> render_sql_template("SELECT count() c FROM where {{sql_and(a=a, b=b)}}", {'b': '2'})
|
|
1894
|
-
("SELECT count() c FROM where b = '2'", {})
|
|
1916
|
+
("SELECT count() c FROM where b = '2'", {}, [])
|
|
1895
1917
|
>>> render_sql_template("SELECT count() c FROM where {{sql_and(a=Int(a, defined=False), b=Int(b, defined=False))}}", {'b': '2'})
|
|
1896
|
-
('SELECT count() c FROM where b = 2', {})
|
|
1918
|
+
('SELECT count() c FROM where b = 2', {}, [])
|
|
1897
1919
|
>>> render_sql_template("SELECT count() c FROM where {{sql_and(a__in=Array(a), b=b)}}", {'a': 'a,b,c','b': '2'})
|
|
1898
|
-
("SELECT count() c FROM where a in ['a','b','c'] and b = '2'", {})
|
|
1920
|
+
("SELECT count() c FROM where a in ['a','b','c'] and b = '2'", {}, [])
|
|
1899
1921
|
>>> render_sql_template("SELECT count() c FROM where {{sql_and(a__not_in=Array(a), b=b)}}", {'a': 'a,b,c','b': '2'})
|
|
1900
|
-
("SELECT count() c FROM where a not in ['a','b','c'] and b = '2'", {})
|
|
1922
|
+
("SELECT count() c FROM where a not in ['a','b','c'] and b = '2'", {}, [])
|
|
1901
1923
|
>>> render_sql_template("SELECT c FROM where a > {{Date(start)}}", test_mode=True)
|
|
1902
|
-
("SELECT c FROM where a > '2019-01-01'", {})
|
|
1924
|
+
("SELECT c FROM where a > '2019-01-01'", {}, [])
|
|
1903
1925
|
>>> render_sql_template("SELECT c FROM where a > {{DateTime(start)}}", test_mode=True)
|
|
1904
|
-
("SELECT c FROM where a > '2019-01-01 00:00:00'", {})
|
|
1926
|
+
("SELECT c FROM where a > '2019-01-01 00:00:00'", {}, [])
|
|
1905
1927
|
>>> render_sql_template("SELECT c FROM where a > {{DateTime(start)}}", {'start': '2018-09-07 23:55:00'})
|
|
1906
|
-
("SELECT c FROM where a > '2018-09-07 23:55:00'", {})
|
|
1928
|
+
("SELECT c FROM where a > '2018-09-07 23:55:00'", {}, [])
|
|
1907
1929
|
>>> render_sql_template('SELECT * FROM tracker {% if defined(start) %} {{DateTime(start)}} and {{DateTime(end)}} {% end %}', {'start': '2019-08-01 00:00:00', 'end': '2019-08-02 00:00:00'})
|
|
1908
|
-
("SELECT * FROM tracker '2019-08-01 00:00:00' and '2019-08-02 00:00:00' ", {})
|
|
1930
|
+
("SELECT * FROM tracker '2019-08-01 00:00:00' and '2019-08-02 00:00:00' ", {}, [])
|
|
1909
1931
|
>>> render_sql_template('SELECT * from test limit {{Int(limit)}}', test_mode=True)
|
|
1910
|
-
('SELECT * from test limit 0', {})
|
|
1932
|
+
('SELECT * from test limit 0', {}, [])
|
|
1911
1933
|
>>> render_sql_template('SELECT {{symbol(attr)}} from test', test_mode=True)
|
|
1912
|
-
('SELECT `placeholder` from test', {})
|
|
1934
|
+
('SELECT `placeholder` from test', {}, [])
|
|
1913
1935
|
>>> render_sql_template('SELECT {{Array(foo)}}', {'foo': 'a,b,c,d'})
|
|
1914
|
-
("SELECT ['a','b','c','d']", {})
|
|
1936
|
+
("SELECT ['a','b','c','d']", {}, [])
|
|
1915
1937
|
>>> render_sql_template("SELECT {{Array(foo, 'Int32')}}", {'foo': '1,2,3,4'})
|
|
1916
|
-
('SELECT [1,2,3,4]', {})
|
|
1938
|
+
('SELECT [1,2,3,4]', {}, [])
|
|
1917
1939
|
>>> render_sql_template("SELECT {{Array(foo, 'Int32')}}", test_mode=True)
|
|
1918
|
-
('SELECT [0,0]', {})
|
|
1940
|
+
('SELECT [0,0]', {}, [])
|
|
1919
1941
|
>>> render_sql_template("SELECT {{Array(foo)}}", test_mode=True)
|
|
1920
|
-
("SELECT ['__placeholder__0','__placeholder__1']", {})
|
|
1942
|
+
("SELECT ['__placeholder__0','__placeholder__1']", {}, [])
|
|
1921
1943
|
>>> render_sql_template("{{max_threads(2)}} SELECT 1")
|
|
1922
|
-
('-- max_threads 2\\n SELECT 1', {'max_threads': 2})
|
|
1944
|
+
('-- max_threads 2\\n SELECT 1', {'max_threads': 2}, [])
|
|
1923
1945
|
>>> render_sql_template("SELECT {{String(foo)}}", test_mode=True)
|
|
1924
|
-
("SELECT '__placeholder__'", {})
|
|
1946
|
+
("SELECT '__placeholder__'", {}, [])
|
|
1925
1947
|
>>> render_sql_template("SELECT {{String(foo, 'test')}}", test_mode=True)
|
|
1926
|
-
("SELECT 'test'", {})
|
|
1948
|
+
("SELECT 'test'", {}, [])
|
|
1927
1949
|
>>> render_sql_template("SELECT {{String(foo, 'test')}}", {'foo': 'tt'})
|
|
1928
|
-
("SELECT 'tt'", {})
|
|
1950
|
+
("SELECT 'tt'", {}, [])
|
|
1929
1951
|
>>> render_sql_template("SELECT {{String(format, 'test')}}", {'format': 'tt'})
|
|
1930
1952
|
Traceback (most recent call last):
|
|
1931
1953
|
...
|
|
@@ -1943,25 +1965,25 @@ def render_sql_template(
|
|
|
1943
1965
|
...
|
|
1944
1966
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Missing column() default value, use `column(column_name, 'default_column_name')`
|
|
1945
1967
|
>>> render_sql_template("SELECT {{column(agg)}}", {'agg': 'foo'})
|
|
1946
|
-
('SELECT `foo`', {})
|
|
1968
|
+
('SELECT `foo`', {}, [])
|
|
1947
1969
|
>>> render_sql_template("SELECT {{column(agg)}}", {'agg': '"foo"'})
|
|
1948
|
-
('SELECT `foo`', {})
|
|
1970
|
+
('SELECT `foo`', {}, [])
|
|
1949
1971
|
>>> render_sql_template('{% if not defined(test) %}error("This is an error"){% end %}', {})
|
|
1950
|
-
('error("This is an error")', {})
|
|
1972
|
+
('error("This is an error")', {}, [])
|
|
1951
1973
|
>>> render_sql_template('{% if not defined(test) %}custom_error({error: "This is an error"}){% end %}', {})
|
|
1952
|
-
('custom_error({error: "This is an error"})', {})
|
|
1974
|
+
('custom_error({error: "This is an error"})', {}, [])
|
|
1953
1975
|
>>> render_sql_template("SELECT {{String(foo + 'abcd')}}", test_mode=True)
|
|
1954
|
-
("SELECT '__placeholder__'", {})
|
|
1976
|
+
("SELECT '__placeholder__'", {}, [])
|
|
1955
1977
|
>>> render_sql_template("SELECT {{columns(agg)}}", {})
|
|
1956
1978
|
Traceback (most recent call last):
|
|
1957
1979
|
...
|
|
1958
1980
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Missing columns() default value, use `columns(column_names, 'default_column_name')`
|
|
1959
1981
|
>>> render_sql_template("SELECT {{columns(agg, 'a,b,c')}} FROM table", {})
|
|
1960
|
-
('SELECT `a`,`b`,`c` FROM table', {})
|
|
1982
|
+
('SELECT `a`,`b`,`c` FROM table', {}, [])
|
|
1961
1983
|
>>> render_sql_template("SELECT {{columns(agg, 'a,b,c')}} FROM table", {'agg': 'foo'})
|
|
1962
|
-
('SELECT `foo` FROM table', {})
|
|
1984
|
+
('SELECT `foo` FROM table', {}, [])
|
|
1963
1985
|
>>> render_sql_template("SELECT {{columns('a,b,c')}} FROM table", {})
|
|
1964
|
-
('SELECT `a`,`b`,`c` FROM table', {})
|
|
1986
|
+
('SELECT `a`,`b`,`c` FROM table', {}, [])
|
|
1965
1987
|
>>> render_sql_template("% {% if whatever(passenger_count) %}{% end %}", test_mode=True)
|
|
1966
1988
|
Traceback (most recent call last):
|
|
1967
1989
|
...
|
|
@@ -1971,13 +1993,13 @@ def render_sql_template(
|
|
|
1971
1993
|
...
|
|
1972
1994
|
SyntaxError: invalid syntax
|
|
1973
1995
|
>>> render_sql_template("SELECT * FROM dim_fecha_evento where foo like {{sql_unescape(String(pepe), '%')}}", {"pepe": 'raul_el_bueno_is_the_best_%'})
|
|
1974
|
-
("SELECT * FROM dim_fecha_evento where foo like 'raul_el_bueno_is_the_best_%'", {})
|
|
1996
|
+
("SELECT * FROM dim_fecha_evento where foo like 'raul_el_bueno_is_the_best_%'", {}, [])
|
|
1975
1997
|
>>> render_sql_template("SELECT * FROM table WHERE field={{String(field_filter)}}", {"field_filter": 'action."test run"'})
|
|
1976
|
-
('SELECT * FROM table WHERE field=\\'action.\\\\"test run\\\\"\\'', {})
|
|
1998
|
+
('SELECT * FROM table WHERE field=\\'action.\\\\"test run\\\\"\\'', {}, [])
|
|
1977
1999
|
>>> render_sql_template("SELECT {{Int128(foo)}} as x, {{Int128(bar)}} as y", {'foo': -170141183460469231731687303715884105728, 'bar': 170141183460469231731687303715884105727})
|
|
1978
|
-
("SELECT toInt128('-170141183460469231731687303715884105728') as x, toInt128('170141183460469231731687303715884105727') as y", {})
|
|
2000
|
+
("SELECT toInt128('-170141183460469231731687303715884105728') as x, toInt128('170141183460469231731687303715884105727') as y", {}, [])
|
|
1979
2001
|
>>> render_sql_template("SELECT {{Int256(foo)}} as x, {{Int256(bar)}} as y", {'foo': -57896044618658097711785492504343953926634992332820282019728792003956564819968, 'bar': 57896044618658097711785492504343953926634992332820282019728792003956564819967})
|
|
1980
|
-
("SELECT toInt256('-57896044618658097711785492504343953926634992332820282019728792003956564819968') as x, toInt256('57896044618658097711785492504343953926634992332820282019728792003956564819967') as y", {})
|
|
2002
|
+
("SELECT toInt256('-57896044618658097711785492504343953926634992332820282019728792003956564819968') as x, toInt256('57896044618658097711785492504343953926634992332820282019728792003956564819967') as y", {}, [])
|
|
1981
2003
|
>>> render_sql_template('% SELECT * FROM {% import os %}{{ os.popen("whoami").read() }}')
|
|
1982
2004
|
Traceback (most recent call last):
|
|
1983
2005
|
...
|
|
@@ -1999,21 +2021,21 @@ def render_sql_template(
|
|
|
1999
2021
|
...
|
|
2000
2022
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Invalid BinOp: Pow()
|
|
2001
2023
|
>>> render_sql_template("% SELECT {{Array(click_selector, 'String', 'pre,pro')}}")
|
|
2002
|
-
("% SELECT ['pre','pro']", {})
|
|
2024
|
+
("% SELECT ['pre','pro']", {}, [])
|
|
2003
2025
|
>>> render_sql_template("% SELECT {{Array(click_selector, 'String', 'pre,pro')}}", {'click_selector': 'hi,hello'})
|
|
2004
|
-
("% SELECT ['hi','hello']", {})
|
|
2026
|
+
("% SELECT ['hi','hello']", {}, [])
|
|
2005
2027
|
>>> render_sql_template("% SELECT now() > {{DateTime64(variable, '2020-09-09 10:10:10.000')}}", {})
|
|
2006
|
-
("% SELECT now() > '2020-09-09 10:10:10.000'", {})
|
|
2028
|
+
("% SELECT now() > '2020-09-09 10:10:10.000'", {}, [])
|
|
2007
2029
|
>>> render_sql_template("% SELECT {% if defined(x) %} x, 1", {})
|
|
2008
2030
|
Traceback (most recent call last):
|
|
2009
2031
|
...
|
|
2010
2032
|
tinybird.tornado_template.UnClosedIfError: Missing {% end %} block for if at line 1
|
|
2011
2033
|
>>> render_sql_template("% SELECT * FROM employees WHERE 0 {% for kv in JSON(payload) %} OR department = {{kv['dp']}} {% end %}")
|
|
2012
|
-
('% SELECT * FROM employees WHERE 0 ', {})
|
|
2034
|
+
('% SELECT * FROM employees WHERE 0 ', {}, [])
|
|
2013
2035
|
>>> render_sql_template("% SELECT * FROM employees WHERE 0 {% for kv in JSON(payload, '[{\\"dp\\":\\"Sales\\"}]') %} OR department = {{kv['dp']}} {% end %}")
|
|
2014
|
-
("% SELECT * FROM employees WHERE 0 OR department = 'Sales' ", {})
|
|
2036
|
+
("% SELECT * FROM employees WHERE 0 OR department = 'Sales' ", {}, [])
|
|
2015
2037
|
>>> render_sql_template("% SELECT * FROM employees WHERE 0 {% for kv in JSON(payload) %} OR department = {{kv['dp']}} {% end %}", { 'payload': '[{"dp":"Design"},{"dp":"Marketing"}]'})
|
|
2016
|
-
("% SELECT * FROM employees WHERE 0 OR department = 'Design' OR department = 'Marketing' ", {})
|
|
2038
|
+
("% SELECT * FROM employees WHERE 0 OR department = 'Design' OR department = 'Marketing' ", {}, [])
|
|
2017
2039
|
>>> render_sql_template("% {% for kv in JSON(payload) %} department = {{kv['dp']}} {% end %}", test_mode=True)
|
|
2018
2040
|
Traceback (most recent call last):
|
|
2019
2041
|
...
|
|
@@ -2023,22 +2045,26 @@ def render_sql_template(
|
|
|
2023
2045
|
...
|
|
2024
2046
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error parsing JSON: '' - Expecting value: line 1 column 1 (char 0)
|
|
2025
2047
|
>>> render_sql_template("% {% if defined(test) %}{% set _groupByCSV = ','.join(test) %} SELECT test as aa, {{Array(test, 'String')}} as test, {{_groupByCSV}} as a {% end %}", {"test": "1,2"})
|
|
2026
|
-
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {})
|
|
2048
|
+
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {}, [])
|
|
2027
2049
|
>>> render_sql_template("% {% if defined(test) %}{% set _groupByCSV = ','.join(test) %} SELECT test as aa, {{Array(test, 'String')}} as test, {{_groupByCSV}} as a {% end %}", {"test": ["1","2"]})
|
|
2028
|
-
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {})
|
|
2050
|
+
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {}, [])
|
|
2029
2051
|
>>> render_sql_template("% {% if defined(test) %}{% set _total = sum(test) %} SELECT test as aa, {{Array(test, 'Int32')}} as test, {{_total}} as a {% end %}", {"test": "1,2"})
|
|
2030
|
-
('% SELECT test as aa, [1,2] as test, 3 as a ', {})
|
|
2052
|
+
('% SELECT test as aa, [1,2] as test, 3 as a ', {}, [])
|
|
2031
2053
|
>>> render_sql_template("% {% if defined(test) %}{% set _groupByCSV = ','.join(test) %} SELECT test as aa, {{Array(test, 'String')}} as test, {{_groupByCSV}} as a {% end %}", {"test": ["1","2"]})
|
|
2032
|
-
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {})
|
|
2054
|
+
("% SELECT test as aa, ['1','2'] as test, '1,2' as a ", {}, [])
|
|
2033
2055
|
>>> render_sql_template("% SELECT {% if defined(x) %} x, 1")
|
|
2034
2056
|
Traceback (most recent call last):
|
|
2035
2057
|
...
|
|
2036
2058
|
tinybird.tornado_template.UnClosedIfError: Missing {% end %} block for if at line 1
|
|
2059
|
+
>>> render_sql_template("select * from table where str = {{pipeline}}", { 'pipeline': 'test' })
|
|
2060
|
+
("select * from table where str = 'test'", {}, ['pipeline'])
|
|
2037
2061
|
"""
|
|
2038
2062
|
escape_split_to_array = ff_split_to_array_escape.get(False)
|
|
2039
2063
|
bypass_preprocess_variables = ff_preprocess_parameters_circuit_breaker.get(False)
|
|
2040
2064
|
|
|
2041
|
-
t, template_variables = get_template_and_variables(
|
|
2065
|
+
t, template_variables, variable_warnings = get_template_and_variables(
|
|
2066
|
+
sql, name, escape_arrays=escape_split_to_array
|
|
2067
|
+
)
|
|
2042
2068
|
|
|
2043
2069
|
if not bypass_preprocess_variables and variables is not None:
|
|
2044
2070
|
processed_variables = preprocess_variables(variables, t)
|
|
@@ -2070,7 +2096,7 @@ def render_sql_template(
|
|
|
2070
2096
|
v.update(type_fns)
|
|
2071
2097
|
|
|
2072
2098
|
try:
|
|
2073
|
-
return generate(t, **v)
|
|
2099
|
+
return *generate(t, **v), variable_warnings
|
|
2074
2100
|
except NameError as e:
|
|
2075
2101
|
raise SQLTemplateException(e, documentation="/cli/advanced-templates.html#defined")
|
|
2076
2102
|
except SQLTemplateException:
|
|
@@ -222,7 +222,8 @@ async def get_current_workspace_branches(config: CLIConfig) -> List[Dict[str, An
|
|
|
222
222
|
client = config.get_client()
|
|
223
223
|
user_branches: List[Dict[str, Any]] = (await client.user_workspace_branches()).get("workspaces", [])
|
|
224
224
|
all_branches: List[Dict[str, Any]] = (await client.branches()).get("environments", [])
|
|
225
|
-
branches =
|
|
225
|
+
branches = user_branches + [branch for branch in all_branches if branch not in user_branches]
|
|
226
|
+
|
|
226
227
|
return [branch for branch in branches if branch.get("main") == current_main_workspace["id"]]
|
|
227
228
|
|
|
228
229
|
|
|
@@ -738,15 +738,17 @@ async def datasource_sync(ctx, datasource_name: str, yes: bool):
|
|
|
738
738
|
@datasource.command(name="exchange", hidden=True)
|
|
739
739
|
@click.argument("datasource_a", required=True)
|
|
740
740
|
@click.argument("datasource_b", required=True)
|
|
741
|
+
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
741
742
|
@click.pass_context
|
|
742
743
|
@coro
|
|
743
|
-
async def datasource_exchange(ctx, datasource_a, datasource_b):
|
|
744
|
+
async def datasource_exchange(ctx, datasource_a: str, datasource_b: str, yes: bool):
|
|
744
745
|
"""Exchange two data sources"""
|
|
745
746
|
|
|
746
747
|
client = ctx.obj["client"]
|
|
747
748
|
|
|
748
749
|
try:
|
|
749
|
-
|
|
750
|
+
if yes or click.confirm(FeedbackManager.warning_exchange(datasource_a=datasource_a, datasource_b=datasource_b)):
|
|
751
|
+
await client.datasource_exchange(datasource_a, datasource_b)
|
|
750
752
|
except Exception as e:
|
|
751
753
|
raise CLIDatasourceException(FeedbackManager.error_exception(error=e))
|
|
752
754
|
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import pyperclip
|
|
6
|
+
from click import Context
|
|
7
|
+
from humanfriendly import parse_timespan
|
|
8
|
+
|
|
9
|
+
from tinybird.client import TinyB
|
|
10
|
+
from tinybird.feedback_manager import FeedbackManager
|
|
11
|
+
from tinybird.tb_cli_modules.cli import cli
|
|
12
|
+
from tinybird.tb_cli_modules.common import (
|
|
13
|
+
DoesNotExistException,
|
|
14
|
+
coro,
|
|
15
|
+
echo_safe_humanfriendly_tables_format_smart_table,
|
|
16
|
+
)
|
|
17
|
+
from tinybird.tb_cli_modules.exceptions import CLITokenException
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cli.group()
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def token(ctx: Context) -> None:
|
|
23
|
+
"""Token commands."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@token.command(name="ls")
|
|
27
|
+
@click.option("--match", default=None, help="Retrieve any token matching the pattern. eg --match _test")
|
|
28
|
+
@click.pass_context
|
|
29
|
+
@coro
|
|
30
|
+
async def token_ls(
|
|
31
|
+
ctx: Context,
|
|
32
|
+
match: Optional[str] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""List static tokens."""
|
|
35
|
+
|
|
36
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
37
|
+
client: TinyB = obj["client"]
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
tokens = await client.token_list(match)
|
|
41
|
+
columns = ["id", "name", "description"]
|
|
42
|
+
table = list(map(lambda token: [token.get(key, "") for key in columns], tokens))
|
|
43
|
+
|
|
44
|
+
click.echo(FeedbackManager.info_tokens())
|
|
45
|
+
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
46
|
+
click.echo("\n")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@token.command(name="rm")
|
|
52
|
+
@click.argument("token_id")
|
|
53
|
+
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
54
|
+
@click.pass_context
|
|
55
|
+
@coro
|
|
56
|
+
async def token_rm(ctx: Context, token_id: str, yes: bool) -> None:
|
|
57
|
+
"""Remove a static token."""
|
|
58
|
+
|
|
59
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
60
|
+
client: TinyB = obj["client"]
|
|
61
|
+
if yes or click.confirm(FeedbackManager.warning_confirm_delete_token(token=token_id)):
|
|
62
|
+
try:
|
|
63
|
+
await client.token_delete(token_id)
|
|
64
|
+
except DoesNotExistException:
|
|
65
|
+
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token_id=token_id))
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
68
|
+
click.echo(FeedbackManager.success_delete_token(token=token_id))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@token.command(name="refresh")
|
|
72
|
+
@click.argument("token_id")
|
|
73
|
+
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
74
|
+
@click.pass_context
|
|
75
|
+
@coro
|
|
76
|
+
async def token_refresh(ctx: Context, token_id: str, yes: bool) -> None:
|
|
77
|
+
"""Refresh a static token."""
|
|
78
|
+
|
|
79
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
80
|
+
client: TinyB = obj["client"]
|
|
81
|
+
if yes or click.confirm(FeedbackManager.warning_confirm_refresh_token(token=token_id)):
|
|
82
|
+
try:
|
|
83
|
+
await client.token_refresh(token_id)
|
|
84
|
+
except DoesNotExistException:
|
|
85
|
+
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token=token_id))
|
|
86
|
+
except Exception as e:
|
|
87
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
88
|
+
click.echo(FeedbackManager.success_refresh_token(token=token_id))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@token.command(name="scopes")
|
|
92
|
+
@click.argument("token_id")
|
|
93
|
+
@click.pass_context
|
|
94
|
+
@coro
|
|
95
|
+
async def token_scopes(ctx: Context, token_id: str) -> None:
|
|
96
|
+
"""List static token scopes."""
|
|
97
|
+
|
|
98
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
99
|
+
client: TinyB = obj["client"]
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
scopes = await client.token_scopes(token_id)
|
|
103
|
+
columns = ["type", "resource", "filter"]
|
|
104
|
+
table = list(map(lambda scope: [scope.get(key, "") for key in columns], scopes))
|
|
105
|
+
click.echo(FeedbackManager.info_token_scopes(token=token_id))
|
|
106
|
+
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
107
|
+
click.echo("\n")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@token.command(name="copy")
|
|
113
|
+
@click.argument("token_id")
|
|
114
|
+
@click.pass_context
|
|
115
|
+
@coro
|
|
116
|
+
async def token_copy(ctx: Context, token_id: str) -> None:
|
|
117
|
+
"""Copy a static token."""
|
|
118
|
+
|
|
119
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
120
|
+
client: TinyB = obj["client"]
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
token = await client.token_get(token_id)
|
|
124
|
+
pyperclip.copy(token["token"].strip())
|
|
125
|
+
except DoesNotExistException:
|
|
126
|
+
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token=token_id))
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
129
|
+
click.echo(FeedbackManager.success_copy_token(token=token_id))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse_ttl(ctx, param, value):
|
|
133
|
+
if value is None:
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
seconds = parse_timespan(value)
|
|
137
|
+
return timedelta(seconds=seconds)
|
|
138
|
+
except ValueError:
|
|
139
|
+
raise click.BadParameter(f"Invalid time to live format: {value}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_fixed_params(fixed_params_list):
|
|
143
|
+
parsed_params = []
|
|
144
|
+
for fixed_param in fixed_params_list:
|
|
145
|
+
param_dict = {}
|
|
146
|
+
for param in fixed_param.split(","):
|
|
147
|
+
key, value = param.split("=")
|
|
148
|
+
param_dict[key] = value
|
|
149
|
+
parsed_params.append(param_dict)
|
|
150
|
+
return parsed_params
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@token.group()
|
|
154
|
+
@click.pass_context
|
|
155
|
+
def create(ctx: Context) -> None:
|
|
156
|
+
"""Token creation commands.
|
|
157
|
+
|
|
158
|
+
You can create two types of tokens: JWT or Static.
|
|
159
|
+
|
|
160
|
+
* JWT tokens have a TTL and can only have the PIPES:READ scope.Their main use case is allow your users to call your endpoints without exposing your API key.
|
|
161
|
+
|
|
162
|
+
* Static tokens do not have a TTL and can have any valid scope (DATASOURCES:READ, DATASOURCES:APPEND, DATASOURCES:CREATE, DATASOURCES:DROP, PIPES:CREATE, PIPES:READ, PIPES:DROP).
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
|
|
166
|
+
tb token create jwt my_jwt_token --ttl 1h --scope PIPES:READ --resource my_pipe
|
|
167
|
+
|
|
168
|
+
tb token create static my_static_token --scope PIPES:READ --resource my_pipe
|
|
169
|
+
|
|
170
|
+
tb token create static my_static_token --scope DATASOURCES:READ --resource my_datasource
|
|
171
|
+
|
|
172
|
+
tb token create static my_static_token --scope DATASOURCES:READ --resource my_datasource --filters "column_name=value"
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@create.command(name="jwt")
|
|
178
|
+
@click.argument("name")
|
|
179
|
+
@click.option("--ttl", type=str, callback=parse_ttl, required=True, help="Time to live (e.g., '1h', '30min', '1d')")
|
|
180
|
+
@click.option(
|
|
181
|
+
"--scope",
|
|
182
|
+
multiple=True,
|
|
183
|
+
type=click.Choice(["PIPES:READ"]),
|
|
184
|
+
required=True,
|
|
185
|
+
help="Scope of the token (only PIPES:READ is allowed for JWT tokens)",
|
|
186
|
+
)
|
|
187
|
+
@click.option("--resource", multiple=True, required=True, help="Resource associated with the scope")
|
|
188
|
+
@click.option(
|
|
189
|
+
"--fixed-params", multiple=True, help="Fixed parameters in key=value format, multiple values separated by commas"
|
|
190
|
+
)
|
|
191
|
+
@click.pass_context
|
|
192
|
+
@coro
|
|
193
|
+
async def create_jwt_token(ctx: Context, name: str, ttl: timedelta, scope, resource, fixed_params) -> None:
|
|
194
|
+
"""Create a JWT token with a TTL specify."""
|
|
195
|
+
|
|
196
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
197
|
+
client: TinyB = obj["client"]
|
|
198
|
+
|
|
199
|
+
expiration_time = int((ttl + datetime.now(timezone.utc)).timestamp())
|
|
200
|
+
if len(scope) != len(resource):
|
|
201
|
+
raise CLITokenException(FeedbackManager.error_number_of_scopes_and_resources_mismatch())
|
|
202
|
+
|
|
203
|
+
# Ensure the number of fixed-params does not exceed the number of scope/resource pairs
|
|
204
|
+
if fixed_params and len(fixed_params) > len(scope):
|
|
205
|
+
raise CLITokenException(FeedbackManager.error_number_of_fixed_params_and_resources_mismatch())
|
|
206
|
+
|
|
207
|
+
# Parse fixed params
|
|
208
|
+
parsed_fixed_params = parse_fixed_params(fixed_params) if fixed_params else []
|
|
209
|
+
|
|
210
|
+
# Create a list of fixed params for each scope/resource pair, defaulting to empty dict if not provided
|
|
211
|
+
fixed_params_list: List[Dict[str, Any]] = [{}] * len(scope)
|
|
212
|
+
for i, params in enumerate(parsed_fixed_params):
|
|
213
|
+
fixed_params_list[i] = params
|
|
214
|
+
|
|
215
|
+
scopes = []
|
|
216
|
+
for sc, res, fparams in zip(scope, resource, fixed_params_list):
|
|
217
|
+
scopes.append(
|
|
218
|
+
{
|
|
219
|
+
"type": sc,
|
|
220
|
+
"resource": res,
|
|
221
|
+
"fixed_params": fparams,
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
response = await client.create_jwt_token(name, expiration_time, scopes)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
229
|
+
|
|
230
|
+
click.echo("The token has been generated successfully.")
|
|
231
|
+
click.echo(
|
|
232
|
+
f"The token will expire at: {datetime.fromtimestamp(expiration_time).strftime('%Y-%m-%d %H:%M:%S')} UTC "
|
|
233
|
+
)
|
|
234
|
+
click.echo(f"The token is: {response['token']}")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# Valid scopes for static tokens
|
|
238
|
+
valid_scopes = [
|
|
239
|
+
"DATASOURCES:READ",
|
|
240
|
+
"DATASOURCES:APPEND",
|
|
241
|
+
"DATASOURCES:CREATE",
|
|
242
|
+
"DATASOURCES:DROP",
|
|
243
|
+
"PIPES:CREATE",
|
|
244
|
+
"PIPES:READ",
|
|
245
|
+
"PIPES:DROP",
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# As we are passing dynamic options to the command, we need to create a custom class to handle the help message
|
|
250
|
+
class DynamicOptionsCommand(click.Command):
|
|
251
|
+
def get_help(self, ctx):
|
|
252
|
+
# Usage
|
|
253
|
+
usage = "Usage: tb token create static [OPTIONS] NAME\n\n"
|
|
254
|
+
dynamic_options_help = usage
|
|
255
|
+
|
|
256
|
+
# Description
|
|
257
|
+
dynamic_options_help += " Create a static token that will live forever.\n\n"
|
|
258
|
+
|
|
259
|
+
# Options
|
|
260
|
+
dynamic_options_help += "Options:\n"
|
|
261
|
+
dynamic_options_help += f" --scope [{','.join(valid_scopes)}] Scope for the token [Required]\n"
|
|
262
|
+
dynamic_options_help += " --resource TEXT Resource you want to associate the scope with\n"
|
|
263
|
+
dynamic_options_help += " --filter TEXT SQL condition used to filter the values when calling with this token (eg. --filter=value > 0) \n"
|
|
264
|
+
dynamic_options_help += " -h, --help Show this message and exit.\n"
|
|
265
|
+
|
|
266
|
+
return dynamic_options_help
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@create.command(
|
|
270
|
+
name="static", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=DynamicOptionsCommand
|
|
271
|
+
)
|
|
272
|
+
@click.argument("name")
|
|
273
|
+
@click.pass_context
|
|
274
|
+
@coro
|
|
275
|
+
async def create_static_token(ctx, name: str):
|
|
276
|
+
"""Create a static token."""
|
|
277
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
278
|
+
client: TinyB = obj["client"]
|
|
279
|
+
|
|
280
|
+
args = ctx.args
|
|
281
|
+
scopes: List[Dict[str, str]] = []
|
|
282
|
+
current_scope = None
|
|
283
|
+
|
|
284
|
+
# We parse the arguments to get the scopes, resources and filters
|
|
285
|
+
# The arguments should be in the format --scope <scope> --resource <resource> --filter <filter>
|
|
286
|
+
i = 0
|
|
287
|
+
while i < len(args):
|
|
288
|
+
if args[i] == "--scope":
|
|
289
|
+
if current_scope:
|
|
290
|
+
scopes.append(current_scope)
|
|
291
|
+
current_scope = {}
|
|
292
|
+
current_scope = {"scope": args[i + 1]}
|
|
293
|
+
i += 2
|
|
294
|
+
elif args[i] == "--resource":
|
|
295
|
+
if current_scope is None:
|
|
296
|
+
raise click.BadParameter("Resource must follow a scope")
|
|
297
|
+
if "resource" in current_scope:
|
|
298
|
+
raise click.BadParameter(
|
|
299
|
+
"Resource already defined for this scope. The format is --scope <scope> --resource <resource> --filter <filter>"
|
|
300
|
+
)
|
|
301
|
+
current_scope["resource"] = args[i + 1]
|
|
302
|
+
i += 2
|
|
303
|
+
elif args[i] == "--filter":
|
|
304
|
+
if current_scope is None:
|
|
305
|
+
raise click.BadParameter("Filter must follow a scope")
|
|
306
|
+
if "filter" in current_scope:
|
|
307
|
+
raise click.BadParameter(
|
|
308
|
+
"Filter already defined for this scope. The format is --scope <scope> --resource <resource> --filter <filter>"
|
|
309
|
+
)
|
|
310
|
+
current_scope["filter"] = args[i + 1]
|
|
311
|
+
i += 2
|
|
312
|
+
else:
|
|
313
|
+
raise click.BadParameter(f"Unknown parameter {args[i]}")
|
|
314
|
+
|
|
315
|
+
if current_scope:
|
|
316
|
+
scopes.append(current_scope)
|
|
317
|
+
|
|
318
|
+
# Parse the scopes like `SCOPE:RESOURCE:FILTER` or `SCOPE:RESOURCE` or `SCOPE` as that's what the API expsects
|
|
319
|
+
scoped_parsed: List[str] = []
|
|
320
|
+
for scope in scopes:
|
|
321
|
+
if scope.get("resource") and scope.get("filter"):
|
|
322
|
+
scoped_parsed.append(f"{scope.get('scope')}:{scope.get('resource')}:{scope.get('filter')}")
|
|
323
|
+
elif scope.get("resource"):
|
|
324
|
+
scoped_parsed.append(f"{scope.get('scope')}:{scope.get('resource')}")
|
|
325
|
+
elif "scope" in scope:
|
|
326
|
+
scoped_parsed.append(scope.get("scope", ""))
|
|
327
|
+
else:
|
|
328
|
+
raise CLITokenException("Unknown error")
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
await client.create_token(name, scoped_parsed, origin_code=None)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
334
|
+
|
|
335
|
+
click.echo("The token has been generated successfully.")
|
|
@@ -892,7 +892,15 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
892
892
|
reader.consume(2)
|
|
893
893
|
if not contents:
|
|
894
894
|
reader.raise_parse_error("Empty expression")
|
|
895
|
-
|
|
895
|
+
|
|
896
|
+
try:
|
|
897
|
+
body.chunks.append(_Expression(contents, line))
|
|
898
|
+
except SyntaxError:
|
|
899
|
+
operator, _, _ = contents.partition(" ")
|
|
900
|
+
|
|
901
|
+
if "from" in operator:
|
|
902
|
+
reader.raise_parse_error('"from" is a forbidden word')
|
|
903
|
+
raise
|
|
896
904
|
continue
|
|
897
905
|
|
|
898
906
|
# Block
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1.1
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,15 +18,26 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
+
4.1.1
|
|
22
|
+
************
|
|
23
|
+
|
|
24
|
+
- `Fixed` workspace info gathering when switching branches. We were prioritizing the general workspace properties over the user ones.
|
|
25
|
+
|
|
26
|
+
4.1.0
|
|
27
|
+
************
|
|
28
|
+
|
|
29
|
+
- `Added` `tb token create` command to be able to create static and JWT tokens from the CLI. You can check more information at https://www.tinybird.co/blog-posts/jwt-api-endpoints-public-beta
|
|
30
|
+
- `Fixed` `tb init --git` to pin `tinybird-cli>=4,<5` in `requirements.txt` to avoid issues with the latest version of the CLI.
|
|
31
|
+
|
|
21
32
|
4.0.0
|
|
22
33
|
************
|
|
23
34
|
|
|
24
|
-
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to
|
|
35
|
+
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to this version.
|
|
25
36
|
|
|
26
37
|
- `Deprecated` `--semver` flag and `tb release` commands are now deprecated. You can keep using `tb deploy` to integrate and deploy from git. Changes are deployed to the main Workspace instead of to a Release.
|
|
27
|
-
- `Removed` `--cicd` flag and CI/CD templates generation from `tb init`. You can still use the git integration, just create your own pipelines. You can use the ones in this repo as an
|
|
38
|
+
- `Removed` `--cicd` flag and CI/CD templates generation from `tb init`. You can still use the git integration, just create your own pipelines. You can use the ones in this repo as an example https://github.com/tinybirdco/ci
|
|
28
39
|
- `Removed` `tb env` command is removed, use `tb branch` instead.
|
|
29
|
-
- `Deprecated` .datasource files with `ENGINE "Join"`
|
|
40
|
+
- `Deprecated` .datasource files with `ENGINE "Join"` is deprecated, use `Engine "MergeeTree"` instead.
|
|
30
41
|
- `Deprecated` `tb materialize`
|
|
31
42
|
- `Removed` Drop the `--timeout` flag from `tb push` which made the populate job to timeout. You can use now `--wait` to wait for the job to finish or nothing to just create the job and return.
|
|
32
43
|
- `Removed` Support for `KEY` directive is removed. The `KEY` was used to create a Data Source with Join engine by the given `KEY` column name. Join engines are also deprecated, you can use a regular `MergeTree` Data Source instead and adapt the pipes SQL accordingly.
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, Optional
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
import pyperclip
|
|
5
|
-
from click import Context
|
|
6
|
-
|
|
7
|
-
from tinybird.client import TinyB
|
|
8
|
-
from tinybird.feedback_manager import FeedbackManager
|
|
9
|
-
from tinybird.tb_cli_modules.cli import cli
|
|
10
|
-
from tinybird.tb_cli_modules.common import (
|
|
11
|
-
DoesNotExistException,
|
|
12
|
-
coro,
|
|
13
|
-
echo_safe_humanfriendly_tables_format_smart_table,
|
|
14
|
-
)
|
|
15
|
-
from tinybird.tb_cli_modules.exceptions import CLITokenException
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@cli.group()
|
|
19
|
-
@click.pass_context
|
|
20
|
-
def token(ctx: Context) -> None:
|
|
21
|
-
"""Token commands."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@token.command(name="ls")
|
|
25
|
-
@click.option("--match", default=None, help="Retrieve any token matching the pattern. eg --match _test")
|
|
26
|
-
@click.pass_context
|
|
27
|
-
@coro
|
|
28
|
-
async def token_ls(
|
|
29
|
-
ctx: Context,
|
|
30
|
-
match: Optional[str] = None,
|
|
31
|
-
) -> None:
|
|
32
|
-
"""List tokens."""
|
|
33
|
-
|
|
34
|
-
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
35
|
-
client: TinyB = obj["client"]
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
tokens = await client.token_list(match)
|
|
39
|
-
columns = ["id", "name", "description"]
|
|
40
|
-
table = list(map(lambda token: [token.get(key, "") for key in columns], tokens))
|
|
41
|
-
|
|
42
|
-
click.echo(FeedbackManager.info_tokens())
|
|
43
|
-
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
44
|
-
click.echo("\n")
|
|
45
|
-
except Exception as e:
|
|
46
|
-
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@token.command(name="rm")
|
|
50
|
-
@click.argument("token_id")
|
|
51
|
-
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
52
|
-
@click.pass_context
|
|
53
|
-
@coro
|
|
54
|
-
async def token_rm(ctx: Context, token_id: str, yes: bool) -> None:
|
|
55
|
-
"""Remove a token."""
|
|
56
|
-
|
|
57
|
-
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
58
|
-
client: TinyB = obj["client"]
|
|
59
|
-
if yes or click.confirm(FeedbackManager.warning_confirm_delete_token(token=token_id)):
|
|
60
|
-
try:
|
|
61
|
-
await client.token_delete(token_id)
|
|
62
|
-
except DoesNotExistException:
|
|
63
|
-
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token_id=token_id))
|
|
64
|
-
except Exception as e:
|
|
65
|
-
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
66
|
-
click.echo(FeedbackManager.success_delete_token(token=token_id))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@token.command(name="refresh")
|
|
70
|
-
@click.argument("token_id")
|
|
71
|
-
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
72
|
-
@click.pass_context
|
|
73
|
-
@coro
|
|
74
|
-
async def token_refresh(ctx: Context, token_id: str, yes: bool) -> None:
|
|
75
|
-
"""Refresh a token."""
|
|
76
|
-
|
|
77
|
-
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
78
|
-
client: TinyB = obj["client"]
|
|
79
|
-
if yes or click.confirm(FeedbackManager.warning_confirm_refresh_token(token=token_id)):
|
|
80
|
-
try:
|
|
81
|
-
await client.token_refresh(token_id)
|
|
82
|
-
except DoesNotExistException:
|
|
83
|
-
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token=token_id))
|
|
84
|
-
except Exception as e:
|
|
85
|
-
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
86
|
-
click.echo(FeedbackManager.success_refresh_token(token=token_id))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@token.command(name="scopes")
|
|
90
|
-
@click.argument("token_id")
|
|
91
|
-
@click.pass_context
|
|
92
|
-
@coro
|
|
93
|
-
async def token_scopes(ctx: Context, token_id: str) -> None:
|
|
94
|
-
"""List token scopes."""
|
|
95
|
-
|
|
96
|
-
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
97
|
-
client: TinyB = obj["client"]
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
scopes = await client.token_scopes(token_id)
|
|
101
|
-
columns = ["type", "resource", "filter"]
|
|
102
|
-
table = list(map(lambda scope: [scope.get(key, "") for key in columns], scopes))
|
|
103
|
-
click.echo(FeedbackManager.info_token_scopes(token=token_id))
|
|
104
|
-
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
105
|
-
click.echo("\n")
|
|
106
|
-
except Exception as e:
|
|
107
|
-
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@token.command(name="copy")
|
|
111
|
-
@click.argument("token_id")
|
|
112
|
-
@click.pass_context
|
|
113
|
-
@coro
|
|
114
|
-
async def token_copy(ctx: Context, token_id: str) -> None:
|
|
115
|
-
"""Copy a token."""
|
|
116
|
-
|
|
117
|
-
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
118
|
-
client: TinyB = obj["client"]
|
|
119
|
-
|
|
120
|
-
try:
|
|
121
|
-
token = await client.token_get(token_id)
|
|
122
|
-
pyperclip.copy(token["token"].strip())
|
|
123
|
-
except DoesNotExistException:
|
|
124
|
-
raise CLITokenException(FeedbackManager.error_token_does_not_exist(token=token_id))
|
|
125
|
-
except Exception as e:
|
|
126
|
-
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
127
|
-
click.echo(FeedbackManager.success_copy_token(token=token_id))
|
|
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
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-4.0.2.dev0 → tinybird-cli-4.1.1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|