tinybird-cli 5.7.1.dev0__tar.gz → 5.8.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/PKG-INFO +11 -1
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/__cli__.py +2 -2
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/client.py +56 -4
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/datafile.py +185 -27
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/feedback_manager.py +15 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli.py +1 -0
- tinybird-cli-5.8.0.dev1/tinybird/tb_cli_modules/tag.py +116 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/PKG-INFO +11 -1
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/SOURCES.txt +1 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/setup.cfg +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/check_pypi.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/config.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/connectors.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/context.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/datatypes.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/git_settings.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/sql.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/sql_template.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/sql_toolset.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/syncasync.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/common.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tornado_template.py +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/requires.txt +0 -0
- {tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.8.0.dev1
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,6 +18,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
+
5.8.0.dev1
|
|
22
|
+
***********
|
|
23
|
+
|
|
24
|
+
- `Added` new `tb tag` command.
|
|
25
|
+
|
|
26
|
+
5.8.0.dev0
|
|
27
|
+
***********
|
|
28
|
+
|
|
29
|
+
- `Added` support to `TAGS` in `tb pull` and `tb push`. Allows tagging resources for filtering in the UI.
|
|
30
|
+
|
|
21
31
|
5.7.0
|
|
22
32
|
**********
|
|
23
33
|
|
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '5.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '5.8.0.dev1'
|
|
8
|
+
__revision__ = 'a3056db'
|
|
@@ -103,12 +103,20 @@ class TinyB(object):
|
|
|
103
103
|
self.semver = semver
|
|
104
104
|
|
|
105
105
|
async def _req(
|
|
106
|
-
self,
|
|
106
|
+
self,
|
|
107
|
+
endpoint: str,
|
|
108
|
+
data=None,
|
|
109
|
+
files=None,
|
|
110
|
+
method: str = "GET",
|
|
111
|
+
retries: int = LIMIT_RETRIES,
|
|
112
|
+
use_token: Optional[str] = None,
|
|
113
|
+
**kwargs,
|
|
107
114
|
): # noqa: C901
|
|
108
115
|
url = f"{self.host.strip('/')}/{endpoint.strip('/')}"
|
|
109
116
|
|
|
110
|
-
if self.token
|
|
111
|
-
|
|
117
|
+
token_to_use = use_token if use_token else self.token
|
|
118
|
+
if token_to_use:
|
|
119
|
+
url += ("&" if "?" in endpoint else "?") + "token=" + token_to_use
|
|
112
120
|
if self.version:
|
|
113
121
|
url += ("&" if "?" in url else "?") + "cli_version=" + quote(self.version)
|
|
114
122
|
if self.semver:
|
|
@@ -162,7 +170,7 @@ class TinyB(object):
|
|
|
162
170
|
|
|
163
171
|
if response.status_code == 403:
|
|
164
172
|
error = parse_error_response(response)
|
|
165
|
-
if not
|
|
173
|
+
if not token_to_use:
|
|
166
174
|
raise AuthNoTokenException(f"Forbidden: {error}")
|
|
167
175
|
raise AuthException(f"Forbidden: {error}")
|
|
168
176
|
if response.status_code == 204 or response.status_code == 205:
|
|
@@ -1210,3 +1218,47 @@ class TinyB(object):
|
|
|
1210
1218
|
|
|
1211
1219
|
async def check_auth_login(self) -> Dict[str, Any]:
|
|
1212
1220
|
return await self._req("/v0/auth")
|
|
1221
|
+
|
|
1222
|
+
async def get_all_tags(self, token: Optional[str] = None) -> Dict[str, Any]:
|
|
1223
|
+
return await self._req("/v0/tags", use_token=token)
|
|
1224
|
+
|
|
1225
|
+
async def create_tag_with_resource(
|
|
1226
|
+
self, name: str, resource_id: str, resource_name: str, resource_type: str, token: Optional[str] = None
|
|
1227
|
+
):
|
|
1228
|
+
return await self._req(
|
|
1229
|
+
"/v0/tags",
|
|
1230
|
+
method="POST",
|
|
1231
|
+
headers={"Content-Type": "application/json"},
|
|
1232
|
+
data=json.dumps(
|
|
1233
|
+
{
|
|
1234
|
+
"name": name,
|
|
1235
|
+
"resources": [{"id": resource_id, "name": resource_name, "type": resource_type}],
|
|
1236
|
+
}
|
|
1237
|
+
),
|
|
1238
|
+
use_token=token,
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
async def create_tag(self, name: str, token: Optional[str] = None):
|
|
1242
|
+
return await self._req(
|
|
1243
|
+
"/v0/tags",
|
|
1244
|
+
method="POST",
|
|
1245
|
+
headers={"Content-Type": "application/json"},
|
|
1246
|
+
data=json.dumps({"name": name}),
|
|
1247
|
+
use_token=token,
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
async def update_tag(self, name: str, resources: List[Dict[str, Any]], token: Optional[str] = None):
|
|
1251
|
+
await self._req(
|
|
1252
|
+
f"/v0/tags/{name}",
|
|
1253
|
+
method="PUT",
|
|
1254
|
+
headers={"Content-Type": "application/json"},
|
|
1255
|
+
data=json.dumps(
|
|
1256
|
+
{
|
|
1257
|
+
"resources": resources,
|
|
1258
|
+
}
|
|
1259
|
+
),
|
|
1260
|
+
use_token=token,
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
async def delete_tag(self, name: str, token: Optional[str] = None):
|
|
1264
|
+
await self._req(f"/v0/tags/{name}", method="DELETE", use_token=token)
|
|
@@ -760,6 +760,7 @@ class Datafile:
|
|
|
760
760
|
self.includes: Dict[str, Any] = {}
|
|
761
761
|
self.shared_with: List[str] = []
|
|
762
762
|
self.warnings: List[str] = []
|
|
763
|
+
self.filtering_tags: Optional[List[str]] = None
|
|
763
764
|
|
|
764
765
|
def validate(self) -> None:
|
|
765
766
|
for x in self.nodes:
|
|
@@ -903,6 +904,32 @@ def eval_var(s: str, skip: bool = False) -> str:
|
|
|
903
904
|
return Template(s).safe_substitute(os.environ)
|
|
904
905
|
|
|
905
906
|
|
|
907
|
+
def parse_tags(tags: str) -> Tuple[Dict[str, str], List[str]]:
|
|
908
|
+
"""
|
|
909
|
+
Parses a string of tags into:
|
|
910
|
+
- kv_tags: a dictionary of key-value tags: the previous tags we have for operational purposes. They
|
|
911
|
+
have the format key=value&key2=value2 (with_staging=true&with_last_date=true)
|
|
912
|
+
- filtering_tags: a list of tags that are used for filtering.
|
|
913
|
+
|
|
914
|
+
Example: "with_staging=true&with_last_date=true,billing,stats" ->
|
|
915
|
+
kv_tags = {"with_staging": "true", "with_last_date": "true"}
|
|
916
|
+
filtering_tags = ["billing", "stats"]
|
|
917
|
+
"""
|
|
918
|
+
kv_tags = {}
|
|
919
|
+
filtering_tags = []
|
|
920
|
+
|
|
921
|
+
entries = tags.split(",")
|
|
922
|
+
for entry in entries:
|
|
923
|
+
trimmed_entry = entry.strip()
|
|
924
|
+
if "=" in trimmed_entry:
|
|
925
|
+
the_tags = {k: v[0] for k, v in urllib.parse.parse_qs(trimmed_entry).items()}
|
|
926
|
+
kv_tags.update(the_tags)
|
|
927
|
+
else:
|
|
928
|
+
filtering_tags.append(trimmed_entry)
|
|
929
|
+
|
|
930
|
+
return kv_tags, filtering_tags
|
|
931
|
+
|
|
932
|
+
|
|
906
933
|
def parse(
|
|
907
934
|
s: str,
|
|
908
935
|
default_node: Optional[str] = None,
|
|
@@ -1100,6 +1127,16 @@ def parse(
|
|
|
1100
1127
|
|
|
1101
1128
|
return _f
|
|
1102
1129
|
|
|
1130
|
+
def tags(*args: str, **kwargs: Any) -> None:
|
|
1131
|
+
# Pipe nodes or Data Sources
|
|
1132
|
+
if parser_state.current_node:
|
|
1133
|
+
assign_node_var("tags")(*args, **kwargs)
|
|
1134
|
+
else:
|
|
1135
|
+
# Pipes, at pipe level
|
|
1136
|
+
raw_tags = _unquote((" ".join(args)).strip())
|
|
1137
|
+
_, filtering_tags = parse_tags(raw_tags)
|
|
1138
|
+
doc.filtering_tags = filtering_tags
|
|
1139
|
+
|
|
1103
1140
|
cmds = {
|
|
1104
1141
|
"from": assign("from"),
|
|
1105
1142
|
"source": sources,
|
|
@@ -1120,7 +1157,7 @@ def parse(
|
|
|
1120
1157
|
"description": description,
|
|
1121
1158
|
"type": assign_node_var("type"),
|
|
1122
1159
|
"datasource": assign_node_var("datasource"),
|
|
1123
|
-
"tags":
|
|
1160
|
+
"tags": tags,
|
|
1124
1161
|
"target_datasource": assign_node_var("target_datasource"),
|
|
1125
1162
|
"copy_schedule": assign_node_var(CopyParameters.COPY_SCHEDULE),
|
|
1126
1163
|
"copy_mode": assign_node_var("mode"),
|
|
@@ -1515,8 +1552,9 @@ async def process_file(
|
|
|
1515
1552
|
del params["format"]
|
|
1516
1553
|
|
|
1517
1554
|
if "tags" in node:
|
|
1518
|
-
tags
|
|
1555
|
+
tags, filtering_tags = parse_tags(node["tags"])
|
|
1519
1556
|
params.update(tags)
|
|
1557
|
+
doc.filtering_tags = filtering_tags
|
|
1520
1558
|
|
|
1521
1559
|
resources: List[Dict[str, Any]] = []
|
|
1522
1560
|
|
|
@@ -1530,6 +1568,7 @@ async def process_file(
|
|
|
1530
1568
|
"deps": deps,
|
|
1531
1569
|
"tokens": doc.tokens,
|
|
1532
1570
|
"shared_with": doc.shared_with,
|
|
1571
|
+
"filtering_tags": doc.filtering_tags,
|
|
1533
1572
|
}
|
|
1534
1573
|
)
|
|
1535
1574
|
|
|
@@ -1615,7 +1654,7 @@ async def process_file(
|
|
|
1615
1654
|
sql = re.sub("([\t \\n']+|^)" + old + "([\t \\n'\\)]+|$)", "\\1" + new + "\\2", sql)
|
|
1616
1655
|
|
|
1617
1656
|
if "tags" in node:
|
|
1618
|
-
tags =
|
|
1657
|
+
tags, _ = parse_tags(node["tags"]) # Only interested in the "old" tags
|
|
1619
1658
|
params.update(tags)
|
|
1620
1659
|
|
|
1621
1660
|
nodes.append(
|
|
@@ -1638,6 +1677,7 @@ async def process_file(
|
|
|
1638
1677
|
"tokens": doc.tokens,
|
|
1639
1678
|
"description": description,
|
|
1640
1679
|
"warnings": doc.warnings,
|
|
1680
|
+
"filtering_tags": doc.filtering_tags,
|
|
1641
1681
|
}
|
|
1642
1682
|
]
|
|
1643
1683
|
elif DataFileExtensions.TOKEN in filename:
|
|
@@ -2646,6 +2686,121 @@ def show_materialized_view_warnings(warnings):
|
|
|
2646
2686
|
)
|
|
2647
2687
|
|
|
2648
2688
|
|
|
2689
|
+
async def update_tags(resource_id: str, resource_name: str, resource_type: str, tags: List[str], tb_client: TinyB):
|
|
2690
|
+
def get_tags_for_resource(all_tags: dict, resource_id: str, resource_name: str) -> List[str]:
|
|
2691
|
+
tag_names = []
|
|
2692
|
+
|
|
2693
|
+
for tag in all_tags.get("tags", []):
|
|
2694
|
+
for resource in tag.get("resources", []):
|
|
2695
|
+
if resource.get("id") == resource_id or resource.get("name") == resource_name:
|
|
2696
|
+
tag_names.append(tag.get("name"))
|
|
2697
|
+
break # No need to check other resources in this tag
|
|
2698
|
+
|
|
2699
|
+
return tag_names
|
|
2700
|
+
|
|
2701
|
+
def get_tag(all_tags: dict, tag_name: str) -> Optional[dict]:
|
|
2702
|
+
for tag in all_tags.get("tags", []):
|
|
2703
|
+
if tag.get("name") == tag_name:
|
|
2704
|
+
return tag
|
|
2705
|
+
return None
|
|
2706
|
+
|
|
2707
|
+
def compare_tags(current_tags: List[str], new_tags: List[str]) -> Tuple[List[str], List[str]]:
|
|
2708
|
+
tags_to_add = list(set(new_tags) - set(current_tags))
|
|
2709
|
+
tags_to_remove = list(set(current_tags) - set(new_tags))
|
|
2710
|
+
return tags_to_add, tags_to_remove
|
|
2711
|
+
|
|
2712
|
+
token_from_main = await get_token_from_main_branch(tb_client)
|
|
2713
|
+
|
|
2714
|
+
try:
|
|
2715
|
+
all_tags = await tb_client.get_all_tags(token=token_from_main)
|
|
2716
|
+
except Exception as e:
|
|
2717
|
+
raise Exception(FeedbackManager.error_getting_tags(error=str(e)))
|
|
2718
|
+
|
|
2719
|
+
# Get all tags of that resource
|
|
2720
|
+
current_tags = get_tags_for_resource(all_tags, resource_id, resource_name)
|
|
2721
|
+
|
|
2722
|
+
# Get the tags to add and remove
|
|
2723
|
+
tags_to_add, tags_to_remove = compare_tags(current_tags, tags)
|
|
2724
|
+
|
|
2725
|
+
# Tags to add
|
|
2726
|
+
for tag_name in tags_to_add:
|
|
2727
|
+
tag = get_tag(all_tags, tag_name)
|
|
2728
|
+
|
|
2729
|
+
if not tag:
|
|
2730
|
+
# Create new tag
|
|
2731
|
+
try:
|
|
2732
|
+
await tb_client.create_tag_with_resource(
|
|
2733
|
+
name=tag_name,
|
|
2734
|
+
resource_id=resource_id,
|
|
2735
|
+
resource_name=resource_name,
|
|
2736
|
+
resource_type=resource_type,
|
|
2737
|
+
token=token_from_main,
|
|
2738
|
+
)
|
|
2739
|
+
except Exception as e:
|
|
2740
|
+
raise Exception(FeedbackManager.error_creating_tag(error=str(e)))
|
|
2741
|
+
else:
|
|
2742
|
+
# Update tag with new resource
|
|
2743
|
+
resources = tag.get("resources", [])
|
|
2744
|
+
resources.append({"id": resource_id, "name": resource_name, "type": resource_type})
|
|
2745
|
+
try:
|
|
2746
|
+
await tb_client.update_tag(tag.get("name", tag_name), resources, token=token_from_main)
|
|
2747
|
+
except Exception as e:
|
|
2748
|
+
raise Exception(FeedbackManager.error_updating_tag(error=str(e)))
|
|
2749
|
+
|
|
2750
|
+
# Tags to delete
|
|
2751
|
+
for tag_name in tags_to_remove:
|
|
2752
|
+
tag = get_tag(all_tags, tag_name)
|
|
2753
|
+
|
|
2754
|
+
if tag:
|
|
2755
|
+
resources = tag.get("resources", [])
|
|
2756
|
+
resources = [resource for resource in resources if resource.get("name") != resource_name]
|
|
2757
|
+
try:
|
|
2758
|
+
await tb_client.update_tag(tag.get("name", tag_name), resources, token=token_from_main)
|
|
2759
|
+
except Exception as e:
|
|
2760
|
+
raise Exception(FeedbackManager.error_updating_tag(error=str(e)))
|
|
2761
|
+
|
|
2762
|
+
|
|
2763
|
+
async def update_tags_in_resource(rs: Dict[str, Any], resource_type: str, client: TinyB):
|
|
2764
|
+
filtering_tags = rs.get("filtering_tags", [])
|
|
2765
|
+
|
|
2766
|
+
if not filtering_tags:
|
|
2767
|
+
return
|
|
2768
|
+
|
|
2769
|
+
resource_id = ""
|
|
2770
|
+
resource_name = ""
|
|
2771
|
+
|
|
2772
|
+
if resource_type == "datasource":
|
|
2773
|
+
ds_name = rs["params"]["name"]
|
|
2774
|
+
try:
|
|
2775
|
+
persisted_ds = await client.get_datasource(ds_name)
|
|
2776
|
+
resource_id = persisted_ds.get("id", "")
|
|
2777
|
+
resource_name = persisted_ds.get("name", "")
|
|
2778
|
+
except DoesNotExistException:
|
|
2779
|
+
click.echo(
|
|
2780
|
+
FeedbackManager.error_tag_generic("Could not get the latest Data Source info for updating its tags.")
|
|
2781
|
+
)
|
|
2782
|
+
elif resource_type == "pipe":
|
|
2783
|
+
pipe_name = rs["name"]
|
|
2784
|
+
try:
|
|
2785
|
+
persisted_pipe = await client.pipe(pipe_name)
|
|
2786
|
+
resource_id = persisted_pipe.get("id", "")
|
|
2787
|
+
resource_name = persisted_pipe.get("name", "")
|
|
2788
|
+
except DoesNotExistException:
|
|
2789
|
+
click.echo(FeedbackManager.error_tag_generic("Could not get the latest Pipe info for updating its tags."))
|
|
2790
|
+
|
|
2791
|
+
if resource_id and resource_name:
|
|
2792
|
+
try:
|
|
2793
|
+
await update_tags(
|
|
2794
|
+
resource_id=resource_id,
|
|
2795
|
+
resource_name=resource_name,
|
|
2796
|
+
resource_type=resource_type,
|
|
2797
|
+
tags=filtering_tags,
|
|
2798
|
+
tb_client=client,
|
|
2799
|
+
)
|
|
2800
|
+
except Exception as e:
|
|
2801
|
+
click.echo(FeedbackManager.error_tag_generic(error=str(e)))
|
|
2802
|
+
|
|
2803
|
+
|
|
2649
2804
|
async def get_token_from_main_branch(branch_tb_client: TinyB) -> Optional[str]:
|
|
2650
2805
|
token_from_main_branch = None
|
|
2651
2806
|
current_workspace = await branch_tb_client.workspace_info()
|
|
@@ -3289,32 +3444,33 @@ async def new_ds(
|
|
|
3289
3444
|
|
|
3290
3445
|
if alter_response and make_changes:
|
|
3291
3446
|
# alter operation finished
|
|
3292
|
-
|
|
3293
|
-
# removed replacing by default. When a datasource is removed data is
|
|
3294
|
-
# removed and all the references needs to be updated
|
|
3295
|
-
if (
|
|
3296
|
-
os.getenv("TB_I_KNOW_WHAT_I_AM_DOING")
|
|
3297
|
-
and click.prompt(FeedbackManager.info_ask_for_datasource_confirmation()) == ds_name
|
|
3298
|
-
): # TODO move to CLI
|
|
3299
|
-
try:
|
|
3300
|
-
await client.datasource_delete(ds_name)
|
|
3301
|
-
click.echo(FeedbackManager.success_delete_datasource(datasource=ds_name))
|
|
3302
|
-
except Exception:
|
|
3303
|
-
raise click.ClickException(FeedbackManager.error_removing_datasource(datasource=ds_name))
|
|
3304
|
-
return
|
|
3447
|
+
pass
|
|
3305
3448
|
else:
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
FeedbackManager.
|
|
3315
|
-
|
|
3449
|
+
# removed replacing by default. When a datasource is removed data is
|
|
3450
|
+
# removed and all the references needs to be updated
|
|
3451
|
+
if (
|
|
3452
|
+
os.getenv("TB_I_KNOW_WHAT_I_AM_DOING")
|
|
3453
|
+
and click.prompt(FeedbackManager.info_ask_for_datasource_confirmation()) == ds_name
|
|
3454
|
+
): # TODO move to CLI
|
|
3455
|
+
try:
|
|
3456
|
+
await client.datasource_delete(ds_name)
|
|
3457
|
+
click.echo(FeedbackManager.success_delete_datasource(datasource=ds_name))
|
|
3458
|
+
except Exception:
|
|
3459
|
+
raise click.ClickException(FeedbackManager.error_removing_datasource(datasource=ds_name))
|
|
3460
|
+
return
|
|
3316
3461
|
else:
|
|
3317
|
-
|
|
3462
|
+
if alter_error_message:
|
|
3463
|
+
raise click.ClickException(
|
|
3464
|
+
FeedbackManager.error_datasource_already_exists_and_alter_failed(
|
|
3465
|
+
datasource=ds_name, alter_error_message=alter_error_message
|
|
3466
|
+
)
|
|
3467
|
+
)
|
|
3468
|
+
if promote_error_message:
|
|
3469
|
+
raise click.ClickException(
|
|
3470
|
+
FeedbackManager.error_promoting_datasource(datasource=ds_name, error=promote_error_message)
|
|
3471
|
+
)
|
|
3472
|
+
else:
|
|
3473
|
+
click.echo(FeedbackManager.warning_datasource_already_exists(datasource=ds_name))
|
|
3318
3474
|
|
|
3319
3475
|
|
|
3320
3476
|
async def new_token(token: Dict[str, Any], client: TinyB, force: bool = False):
|
|
@@ -3400,6 +3556,7 @@ async def exec_file(
|
|
|
3400
3556
|
fork_downstream=fork_downstream,
|
|
3401
3557
|
fork=fork,
|
|
3402
3558
|
)
|
|
3559
|
+
await update_tags_in_resource(r, "pipe", tb_client)
|
|
3403
3560
|
elif r["resource"] == "datasources":
|
|
3404
3561
|
await new_ds(
|
|
3405
3562
|
r,
|
|
@@ -3412,6 +3569,7 @@ async def exec_file(
|
|
|
3412
3569
|
fork=fork,
|
|
3413
3570
|
git_release=git_release,
|
|
3414
3571
|
)
|
|
3572
|
+
await update_tags_in_resource(r, "datasource", tb_client)
|
|
3415
3573
|
elif r["resource"] == "tokens":
|
|
3416
3574
|
await new_token(r, tb_client, force)
|
|
3417
3575
|
else:
|
|
@@ -392,6 +392,11 @@ class FeedbackManager:
|
|
|
392
392
|
"Several materialized nodes in pipe {pipe}. Use --node param."
|
|
393
393
|
)
|
|
394
394
|
error_unlinking_pipe_not_linked = error_message("** {pipe} is not linked (MV, Copy, or Sink).")
|
|
395
|
+
error_getting_tags = error_message("Error getting tags: {error}")
|
|
396
|
+
error_creating_tag = error_message("Error creating new tag: {error}")
|
|
397
|
+
error_updating_tag = error_message("Error updating tag: {error}")
|
|
398
|
+
error_tag_generic = error_message("There was an issue updating tags. {error}")
|
|
399
|
+
error_tag_not_found = error_message("Tag {tag_name} not found.")
|
|
395
400
|
|
|
396
401
|
info_incl_relative_path = info_message("** Relative path {path} does not exist, skipping.")
|
|
397
402
|
info_ignoring_incl_file = info_message(
|
|
@@ -606,6 +611,12 @@ Ready? """
|
|
|
606
611
|
warning_pipe_restricted_params = warning_message(
|
|
607
612
|
"** The parameter names {words} and '{last_word}' are reserved words. Please, choose another name or the pipe will not work as expected."
|
|
608
613
|
)
|
|
614
|
+
warning_tag_remove_no_resources = prompt_message(
|
|
615
|
+
"Tag {tag_name} is not used by any resource. Do you want to remove it?"
|
|
616
|
+
)
|
|
617
|
+
warning_tag_remove = prompt_message(
|
|
618
|
+
"Tag {tag_name} is used by {resources_len} resources. Do you want to remove it?"
|
|
619
|
+
)
|
|
609
620
|
|
|
610
621
|
info_fixtures_branch = info_message("** Data Fixtures are only pushed to Branches")
|
|
611
622
|
info_materialize_push_datasource_exists = warning_message("** Data Source {name} already exists")
|
|
@@ -797,6 +808,8 @@ Ready? """
|
|
|
797
808
|
info_release_rollback = info_message(
|
|
798
809
|
"** The following resources IDs are present in the {semver} Release and will be restored:"
|
|
799
810
|
)
|
|
811
|
+
info_tag_list = info_message("** Tags:")
|
|
812
|
+
info_tag_resources = info_message("** Resources tagged by {tag_name}:")
|
|
800
813
|
warning_no_release = warning_message(
|
|
801
814
|
"** Warning: Workspace does not have Releases, run `tb init --git` to activate them."
|
|
802
815
|
)
|
|
@@ -981,5 +994,7 @@ Ready? """
|
|
|
981
994
|
success_delete_token = success_message("** Token '{token}' removed successfully")
|
|
982
995
|
success_refresh_token = success_message("** Token '{token}' refreshed successfully")
|
|
983
996
|
success_copy_token = success_message("** Token '{token}' copied to clipboard")
|
|
997
|
+
success_tag_created = success_message("** Tag '{tag_name}' created!")
|
|
998
|
+
success_tag_removed = success_message("** Tag '{tag_name}' removed!")
|
|
984
999
|
|
|
985
1000
|
debug_running_file = print_message("** Running {file}", bcolors.CGREY)
|
|
@@ -12,6 +12,7 @@ import tinybird.tb_cli_modules.connection
|
|
|
12
12
|
import tinybird.tb_cli_modules.datasource
|
|
13
13
|
import tinybird.tb_cli_modules.job
|
|
14
14
|
import tinybird.tb_cli_modules.pipe
|
|
15
|
+
import tinybird.tb_cli_modules.tag
|
|
15
16
|
import tinybird.tb_cli_modules.test
|
|
16
17
|
import tinybird.tb_cli_modules.token
|
|
17
18
|
import tinybird.tb_cli_modules.workspace
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import Context
|
|
5
|
+
|
|
6
|
+
from tinybird.feedback_manager import FeedbackManager
|
|
7
|
+
from tinybird.tb_cli_modules.cli import cli
|
|
8
|
+
from tinybird.tb_cli_modules.common import (
|
|
9
|
+
coro,
|
|
10
|
+
echo_safe_humanfriendly_tables_format_smart_table,
|
|
11
|
+
get_current_main_workspace,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cli.group()
|
|
16
|
+
@click.pass_context
|
|
17
|
+
def tag(ctx: Context) -> None:
|
|
18
|
+
"""Tag commands"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@tag.command(name="ls")
|
|
22
|
+
@click.argument("tag_name", required=False)
|
|
23
|
+
@click.pass_context
|
|
24
|
+
@coro
|
|
25
|
+
async def tag_ls(ctx: Context, tag_name: Optional[str]) -> None:
|
|
26
|
+
"""List all the tags of the current Workspace or the resources associated to a specific tag."""
|
|
27
|
+
|
|
28
|
+
client = ctx.ensure_object(dict)["client"]
|
|
29
|
+
config = ctx.ensure_object(dict)["config"]
|
|
30
|
+
main_workspace = await get_current_main_workspace(client, config)
|
|
31
|
+
token = main_workspace.get("token") if main_workspace else None
|
|
32
|
+
|
|
33
|
+
response = await client.get_all_tags(token=token)
|
|
34
|
+
|
|
35
|
+
if tag_name:
|
|
36
|
+
the_tag = [tag for tag in response["tags"] if tag["name"] == tag_name]
|
|
37
|
+
|
|
38
|
+
columns = ["name", "id", "type"]
|
|
39
|
+
table = []
|
|
40
|
+
|
|
41
|
+
if len(the_tag) > 0:
|
|
42
|
+
for resource in the_tag[0]["resources"]:
|
|
43
|
+
table.append([resource["name"], resource["id"], resource["type"]])
|
|
44
|
+
|
|
45
|
+
click.echo(FeedbackManager.info_tag_resources(tag_name=tag_name))
|
|
46
|
+
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
columns = ["tag", "resources"]
|
|
50
|
+
table = []
|
|
51
|
+
|
|
52
|
+
for tag in response["tags"]:
|
|
53
|
+
unique_resources = []
|
|
54
|
+
for resource in tag["resources"]:
|
|
55
|
+
if resource.get("name", "") not in unique_resources:
|
|
56
|
+
unique_resources.append(resource) # Reducing by name in case there are duplicates.
|
|
57
|
+
table.append([tag["name"], len(unique_resources)])
|
|
58
|
+
|
|
59
|
+
click.echo(FeedbackManager.info_tag_list())
|
|
60
|
+
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@tag.command(name="create")
|
|
64
|
+
@click.argument("tag_name")
|
|
65
|
+
@click.pass_context
|
|
66
|
+
@coro
|
|
67
|
+
async def tag_create(ctx: Context, tag_name: str) -> None:
|
|
68
|
+
"""Create a tag in the current Workspace."""
|
|
69
|
+
|
|
70
|
+
client = ctx.ensure_object(dict)["client"]
|
|
71
|
+
config = ctx.ensure_object(dict)["config"]
|
|
72
|
+
main_workspace = await get_current_main_workspace(client, config)
|
|
73
|
+
token = main_workspace.get("token") if main_workspace else None
|
|
74
|
+
|
|
75
|
+
await client.create_tag(name=tag_name, token=token)
|
|
76
|
+
|
|
77
|
+
click.echo(FeedbackManager.success_tag_created(tag_name=tag_name))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@tag.command(name="rm")
|
|
81
|
+
@click.argument("tag_name")
|
|
82
|
+
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation to delete the tag.")
|
|
83
|
+
@click.pass_context
|
|
84
|
+
@coro
|
|
85
|
+
async def tag_rm(ctx: Context, tag_name: str, yes: bool) -> None:
|
|
86
|
+
"""Remove a tag from the current Workspace."""
|
|
87
|
+
|
|
88
|
+
client = ctx.ensure_object(dict)["client"]
|
|
89
|
+
config = ctx.ensure_object(dict)["config"]
|
|
90
|
+
main_workspace = await get_current_main_workspace(client, config)
|
|
91
|
+
token = main_workspace.get("token") if main_workspace else None
|
|
92
|
+
|
|
93
|
+
remove_tag = True
|
|
94
|
+
|
|
95
|
+
if not yes:
|
|
96
|
+
all_tags = await client.get_all_tags(token=token)
|
|
97
|
+
the_tag = [tag for tag in all_tags["tags"] if tag["name"] == tag_name]
|
|
98
|
+
if len(the_tag) > 0:
|
|
99
|
+
unique_resources = []
|
|
100
|
+
for resource in the_tag[0]["resources"]:
|
|
101
|
+
if resource.get("name", "") not in unique_resources:
|
|
102
|
+
unique_resources.append(resource) # Reducing by name in case there are duplicates.
|
|
103
|
+
|
|
104
|
+
if len(unique_resources) > 0:
|
|
105
|
+
remove_tag = click.confirm(
|
|
106
|
+
FeedbackManager.warning_tag_remove(tag_name=tag_name, resources_len=len(unique_resources))
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
remove_tag = click.confirm(FeedbackManager.warning_tag_remove_no_resources(tag_name=tag_name))
|
|
110
|
+
else:
|
|
111
|
+
remove_tag = False
|
|
112
|
+
click.echo(FeedbackManager.error_tag_not_found(tag_name=tag_name))
|
|
113
|
+
|
|
114
|
+
if remove_tag:
|
|
115
|
+
await client.delete_tag(tag_name, token=token)
|
|
116
|
+
click.echo(FeedbackManager.success_tag_removed(tag_name=tag_name))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.8.0.dev1
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli/introduction.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -18,6 +18,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
18
18
|
Changelog
|
|
19
19
|
----------
|
|
20
20
|
|
|
21
|
+
5.8.0.dev1
|
|
22
|
+
***********
|
|
23
|
+
|
|
24
|
+
- `Added` new `tb tag` command.
|
|
25
|
+
|
|
26
|
+
5.8.0.dev0
|
|
27
|
+
***********
|
|
28
|
+
|
|
29
|
+
- `Added` support to `TAGS` in `tb pull` and `tb push`. Allows tagging resources for filtering in the UI.
|
|
30
|
+
|
|
21
31
|
5.7.0
|
|
22
32
|
**********
|
|
23
33
|
|
|
@@ -29,6 +29,7 @@ tinybird/tb_cli_modules/exceptions.py
|
|
|
29
29
|
tinybird/tb_cli_modules/job.py
|
|
30
30
|
tinybird/tb_cli_modules/pipe.py
|
|
31
31
|
tinybird/tb_cli_modules/regions.py
|
|
32
|
+
tinybird/tb_cli_modules/tag.py
|
|
32
33
|
tinybird/tb_cli_modules/telemetry.py
|
|
33
34
|
tinybird/tb_cli_modules/test.py
|
|
34
35
|
tinybird/tb_cli_modules/token.py
|
|
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-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
{tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
{tinybird-cli-5.7.1.dev0 → tinybird-cli-5.8.0.dev1}/tinybird_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|