tinybird-cli 4.0.3.dev0__tar.gz → 4.1.1.dev0__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.3.dev0 → tinybird-cli-4.1.1.dev0}/PKG-INFO +7 -7
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/__cli__.py +2 -2
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/client.py +17 -2
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/datafile.py +2 -2
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/feedback_manager.py +10 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/datasource.py +4 -2
- tinybird-cli-4.1.1.dev0/tinybird/tb_cli_modules/token.py +335 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/PKG-INFO +7 -7
- tinybird-cli-4.0.3.dev0/tinybird/tb_cli_modules/token.py +0 -127
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/setup.cfg +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/check_pypi.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/config.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/connectors.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/context.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/datatypes.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/git_settings.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/sql.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/sql_template.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/sql_toolset.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/syncasync.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/common.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tornado_template.py +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/requires.txt +0 -0
- {tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/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.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,21 +18,21 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
-
4.0
|
|
21
|
+
4.1.0
|
|
22
22
|
************
|
|
23
23
|
|
|
24
|
-
- `
|
|
25
|
-
|
|
24
|
+
- `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
|
|
25
|
+
- `Fixed` `tb init --git` to pin `tinybird-cli>=4,<5` in `requirements.txt` to avoid issues with the latest version of the CLI.
|
|
26
26
|
|
|
27
27
|
4.0.0
|
|
28
28
|
************
|
|
29
29
|
|
|
30
|
-
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to
|
|
30
|
+
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to this version.
|
|
31
31
|
|
|
32
32
|
- `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.
|
|
33
|
-
- `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
|
|
33
|
+
- `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
|
|
34
34
|
- `Removed` `tb env` command is removed, use `tb branch` instead.
|
|
35
|
-
- `Deprecated` .datasource files with `ENGINE "Join"`
|
|
35
|
+
- `Deprecated` .datasource files with `ENGINE "Join"` is deprecated, use `Engine "MergeeTree"` instead.
|
|
36
36
|
- `Deprecated` `tb materialize`
|
|
37
37
|
- `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.
|
|
38
38
|
- `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.dev0'
|
|
8
|
+
__revision__ = '4b7296d'
|
|
@@ -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)
|
|
@@ -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.")
|
|
@@ -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.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1.1.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,21 +18,21 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
-
4.0
|
|
21
|
+
4.1.0
|
|
22
22
|
************
|
|
23
23
|
|
|
24
|
-
- `
|
|
25
|
-
|
|
24
|
+
- `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
|
|
25
|
+
- `Fixed` `tb init --git` to pin `tinybird-cli>=4,<5` in `requirements.txt` to avoid issues with the latest version of the CLI.
|
|
26
26
|
|
|
27
27
|
4.0.0
|
|
28
28
|
************
|
|
29
29
|
|
|
30
|
-
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to
|
|
30
|
+
This is a major release, please read the commands affected below and consider updating your scripts and workflow before upgrading to this version.
|
|
31
31
|
|
|
32
32
|
- `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.
|
|
33
|
-
- `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
|
|
33
|
+
- `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
|
|
34
34
|
- `Removed` `tb env` command is removed, use `tb branch` instead.
|
|
35
|
-
- `Deprecated` .datasource files with `ENGINE "Join"`
|
|
35
|
+
- `Deprecated` .datasource files with `ENGINE "Join"` is deprecated, use `Engine "MergeeTree"` instead.
|
|
36
36
|
- `Deprecated` `tb materialize`
|
|
37
37
|
- `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.
|
|
38
38
|
- `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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
{tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
{tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-4.0.3.dev0 → tinybird-cli-4.1.1.dev0}/tinybird_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|