tinybird-cli 6.5.7.dev0__py3-none-any.whl → 6.5.8.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinybird/__cli__.py +2 -2
- tinybird/ch_utils/constants.py +1 -0
- tinybird/ch_utils/engine.py +90 -54
- tinybird/client.py +58 -46
- tinybird/config.py +0 -4
- tinybird/datafile_common.py +67 -44
- tinybird/datatypes.py +34 -12
- tinybird/feedback_manager.py +5 -0
- tinybird/git_settings.py +1 -3
- tinybird/sql.py +3 -10
- tinybird/sql_template.py +121 -98
- tinybird/sql_template_fmt.py +10 -15
- tinybird/sql_toolset.py +63 -11
- tinybird/syncasync.py +9 -54
- tinybird/tb_cli_modules/branch.py +4 -25
- tinybird/tb_cli_modules/cicd.py +2 -0
- tinybird/tb_cli_modules/cli.py +5 -9
- tinybird/tb_cli_modules/common.py +30 -97
- tinybird/tb_cli_modules/config.py +1 -2
- tinybird/tb_cli_modules/datasource.py +2 -2
- tinybird/tb_cli_modules/pipe.py +49 -34
- tinybird/tb_cli_modules/tinyunit/tinyunit.py +4 -4
- tinybird/tb_cli_modules/token.py +1 -2
- tinybird/tb_cli_modules/workspace.py +11 -13
- tinybird/tornado_template.py +227 -83
- tinybird/utils/__init__.py +0 -0
- tinybird/utils/bools.py +19 -0
- {tinybird_cli-6.5.7.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/METADATA +9 -1
- tinybird_cli-6.5.8.dev0.dist-info/RECORD +45 -0
- tinybird_cli-6.5.7.dev0.dist-info/RECORD +0 -43
- {tinybird_cli-6.5.7.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/WHEEL +0 -0
- {tinybird_cli-6.5.7.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird_cli-6.5.7.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/top_level.txt +0 -0
tinybird/tb_cli_modules/token.py
CHANGED
|
@@ -6,11 +6,10 @@ import pyperclip
|
|
|
6
6
|
from click import Context
|
|
7
7
|
from humanfriendly import parse_timespan
|
|
8
8
|
|
|
9
|
-
from tinybird.client import AuthNoTokenException, TinyB
|
|
9
|
+
from tinybird.client import AuthNoTokenException, DoesNotExistException, TinyB
|
|
10
10
|
from tinybird.feedback_manager import FeedbackManager
|
|
11
11
|
from tinybird.tb_cli_modules.cli import cli
|
|
12
12
|
from tinybird.tb_cli_modules.common import (
|
|
13
|
-
DoesNotExistException,
|
|
14
13
|
coro,
|
|
15
14
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
16
15
|
)
|
|
@@ -115,17 +115,16 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
|
|
|
115
115
|
if config["id"] == workspace["id"]:
|
|
116
116
|
if workspace.get("is_branch"):
|
|
117
117
|
raise CLIWorkspaceException(FeedbackManager.error_not_allowed_in_branch(cli="tb"))
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
118
|
+
click.echo(FeedbackManager.info_current_workspace())
|
|
119
|
+
table.append(
|
|
120
|
+
[
|
|
121
|
+
workspace["name"],
|
|
122
|
+
workspace["id"],
|
|
123
|
+
workspace["role"],
|
|
124
|
+
_get_workspace_plan_name(workspace["plan"]),
|
|
125
|
+
True,
|
|
126
|
+
]
|
|
127
|
+
)
|
|
129
128
|
break
|
|
130
129
|
|
|
131
130
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
@@ -178,8 +177,7 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
|
|
|
178
177
|
except Exception as e:
|
|
179
178
|
if "is a Shared Data Source" in str(e):
|
|
180
179
|
raise CLIWorkspaceException(FeedbackManager.error_operation_can_not_be_performed(error=e))
|
|
181
|
-
|
|
182
|
-
raise CLIWorkspaceException(FeedbackManager.error_exception(error=e))
|
|
180
|
+
raise CLIWorkspaceException(FeedbackManager.error_exception(error=e))
|
|
183
181
|
else:
|
|
184
182
|
click.echo(FeedbackManager.info_dry_removing_datasource(datasource=ds_name))
|
|
185
183
|
|
tinybird/tornado_template.py
CHANGED
|
@@ -196,13 +196,14 @@ if you need to include a literal ``{{``, ``{%``, or ``{#`` in the output.
|
|
|
196
196
|
"""
|
|
197
197
|
|
|
198
198
|
import ast
|
|
199
|
+
import builtins
|
|
199
200
|
import datetime
|
|
200
201
|
import linecache
|
|
201
202
|
import os.path
|
|
202
|
-
import posixpath
|
|
203
203
|
import re
|
|
204
204
|
import threading
|
|
205
205
|
from io import StringIO
|
|
206
|
+
from types import MappingProxyType
|
|
206
207
|
from typing import Dict, Union
|
|
207
208
|
|
|
208
209
|
from tornado import escape
|
|
@@ -235,14 +236,12 @@ def filter_whitespace(mode, text):
|
|
|
235
236
|
"""
|
|
236
237
|
if mode == "all":
|
|
237
238
|
return text
|
|
238
|
-
|
|
239
|
+
if mode == "single":
|
|
239
240
|
text = _PATTERN_TAB_SPACE.sub(" ", text)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
elif mode == "oneline":
|
|
241
|
+
return _PATTERN_NEWLINE_SPACE.sub("\n", text)
|
|
242
|
+
if mode == "oneline":
|
|
243
243
|
return _PATTERN_ALL_WHITESPACE.sub(" ", text)
|
|
244
|
-
|
|
245
|
-
raise Exception("invalid whitespace mode %s" % mode)
|
|
244
|
+
raise Exception("invalid whitespace mode %s" % mode)
|
|
246
245
|
|
|
247
246
|
|
|
248
247
|
class Template:
|
|
@@ -346,6 +345,8 @@ class Template:
|
|
|
346
345
|
}
|
|
347
346
|
namespace.update(self.namespace)
|
|
348
347
|
namespace.update(kwargs)
|
|
348
|
+
# Set last so neither the loader namespace nor kwargs can swap in the real builtins
|
|
349
|
+
namespace["__builtins__"] = SAFE_BUILTINS
|
|
349
350
|
exec_in(self.compiled, namespace)
|
|
350
351
|
execute = namespace["_tt_execute"]
|
|
351
352
|
# Clear the traceback module's cache of source data now that
|
|
@@ -460,30 +461,7 @@ class Loader(BaseLoader):
|
|
|
460
461
|
def _create_template(self, name):
|
|
461
462
|
path = os.path.join(self.root, name)
|
|
462
463
|
with open(path, "rb") as f:
|
|
463
|
-
|
|
464
|
-
return template
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
class DictLoader(BaseLoader):
|
|
468
|
-
"""A template loader that loads from a dictionary."""
|
|
469
|
-
|
|
470
|
-
def __init__(self, dict, **kwargs):
|
|
471
|
-
super().__init__(**kwargs)
|
|
472
|
-
self.dict = dict
|
|
473
|
-
|
|
474
|
-
def resolve_path(self, name, parent_path=None):
|
|
475
|
-
if (
|
|
476
|
-
parent_path
|
|
477
|
-
and not parent_path.startswith("<")
|
|
478
|
-
and not parent_path.startswith("/")
|
|
479
|
-
and not name.startswith("/")
|
|
480
|
-
):
|
|
481
|
-
file_dir = posixpath.dirname(parent_path)
|
|
482
|
-
name = posixpath.normpath(posixpath.join(file_dir, name))
|
|
483
|
-
return name
|
|
484
|
-
|
|
485
|
-
def _create_template(self, name):
|
|
486
|
-
return Template(self.dict[name], name=name, loader=self)
|
|
464
|
+
return Template(f.read(), name=name, loader=self)
|
|
487
465
|
|
|
488
466
|
|
|
489
467
|
class _Node:
|
|
@@ -553,45 +531,10 @@ class _ExtendsBlock(_Node):
|
|
|
553
531
|
self.name = name
|
|
554
532
|
|
|
555
533
|
|
|
556
|
-
class _IncludeBlock(_Node):
|
|
557
|
-
def __init__(self, name, reader, line):
|
|
558
|
-
self.name = name
|
|
559
|
-
self.template_name = reader.name
|
|
560
|
-
self.line = line
|
|
561
|
-
|
|
562
|
-
def find_named_blocks(self, loader, named_blocks):
|
|
563
|
-
included = loader.load(self.name, self.template_name)
|
|
564
|
-
included.file.find_named_blocks(loader, named_blocks)
|
|
565
|
-
|
|
566
|
-
def generate(self, writer):
|
|
567
|
-
included = writer.loader.load(self.name, self.template_name)
|
|
568
|
-
with writer.include(included, self.line):
|
|
569
|
-
included.file.body.generate(writer)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
class _ApplyBlock(_Node):
|
|
573
|
-
def __init__(self, method, line, body=None):
|
|
574
|
-
self.method = method
|
|
575
|
-
self.line = line
|
|
576
|
-
self.body = body
|
|
577
|
-
|
|
578
|
-
def each_child(self):
|
|
579
|
-
return (self.body,)
|
|
580
|
-
|
|
581
|
-
def generate(self, writer):
|
|
582
|
-
method_name = "_tt_apply%d" % writer.apply_counter
|
|
583
|
-
writer.apply_counter += 1
|
|
584
|
-
writer.write_line("def %s():" % method_name, self.line)
|
|
585
|
-
with writer.indent():
|
|
586
|
-
writer.write_line("_tt_buffer = []", self.line)
|
|
587
|
-
writer.write_line("_tt_append = _tt_buffer.append", self.line)
|
|
588
|
-
self.body.generate(writer)
|
|
589
|
-
writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
|
|
590
|
-
writer.write_line("_tt_append(_tt_utf8(%s(%s())))" % (self.method, method_name), self.line)
|
|
591
|
-
|
|
592
|
-
|
|
593
534
|
class _ControlBlock(_Node):
|
|
594
535
|
def __init__(self, statement, line, body=None):
|
|
536
|
+
if not disable_template_security_validation.get(False):
|
|
537
|
+
check_valid_control_statement(statement)
|
|
595
538
|
self.statement = statement
|
|
596
539
|
self.line = line
|
|
597
540
|
self.body = body
|
|
@@ -609,6 +552,8 @@ class _ControlBlock(_Node):
|
|
|
609
552
|
|
|
610
553
|
class _IntermediateControlBlock(_Node):
|
|
611
554
|
def __init__(self, statement, line):
|
|
555
|
+
if not disable_template_security_validation.get(False):
|
|
556
|
+
check_valid_control_statement(statement)
|
|
612
557
|
self.statement = statement
|
|
613
558
|
self.line = line
|
|
614
559
|
|
|
@@ -620,6 +565,9 @@ class _IntermediateControlBlock(_Node):
|
|
|
620
565
|
|
|
621
566
|
class _Statement(_Node):
|
|
622
567
|
def __init__(self, statement, line):
|
|
568
|
+
# Backs both {% set %} (statement is the part after "set") and {% break/continue %}
|
|
569
|
+
if not disable_template_security_validation.get(False) and statement not in ("break", "continue"):
|
|
570
|
+
check_valid_set_statement(statement)
|
|
623
571
|
self.statement = statement
|
|
624
572
|
self.line = line
|
|
625
573
|
|
|
@@ -646,11 +594,6 @@ class _Expression(_Node):
|
|
|
646
594
|
writer.write_line("_tt_append(_tt_tmp)", self.line)
|
|
647
595
|
|
|
648
596
|
|
|
649
|
-
class _Module(_Expression):
|
|
650
|
-
def __init__(self, expression, line):
|
|
651
|
-
super().__init__("_tt_modules." + expression, line, raw=True)
|
|
652
|
-
|
|
653
|
-
|
|
654
597
|
class _Text(_Node):
|
|
655
598
|
def __init__(self, value, line, whitespace):
|
|
656
599
|
self.value = value
|
|
@@ -811,10 +754,9 @@ class _TemplateReader:
|
|
|
811
754
|
if stop is not None:
|
|
812
755
|
stop += self.pos
|
|
813
756
|
return self.text[slice(start, stop, step)]
|
|
814
|
-
|
|
757
|
+
if key < 0:
|
|
815
758
|
return self.text[key]
|
|
816
|
-
|
|
817
|
-
return self.text[self.pos + key]
|
|
759
|
+
return self.text[self.pos + key]
|
|
818
760
|
|
|
819
761
|
def __str__(self):
|
|
820
762
|
return self.text[self.pos :]
|
|
@@ -933,12 +875,12 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
933
875
|
continue
|
|
934
876
|
|
|
935
877
|
# End tag
|
|
936
|
-
|
|
878
|
+
if operator == "end":
|
|
937
879
|
if not in_block:
|
|
938
880
|
reader.raise_parse_error("Extra {% end %} block")
|
|
939
881
|
return body
|
|
940
882
|
|
|
941
|
-
|
|
883
|
+
if operator in (
|
|
942
884
|
"extends",
|
|
943
885
|
"include",
|
|
944
886
|
"set",
|
|
@@ -977,7 +919,7 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
977
919
|
body.chunks.append(block)
|
|
978
920
|
continue
|
|
979
921
|
|
|
980
|
-
|
|
922
|
+
if operator in ("apply", "block", "try", "if", "for", "while"):
|
|
981
923
|
# parse inner body recursively
|
|
982
924
|
if operator in ("for", "while"):
|
|
983
925
|
block_body = _parse(reader, template, operator, operator)
|
|
@@ -995,14 +937,13 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
995
937
|
body.chunks.append(block)
|
|
996
938
|
continue
|
|
997
939
|
|
|
998
|
-
|
|
940
|
+
if operator in ("break", "continue"):
|
|
999
941
|
if not in_loop:
|
|
1000
942
|
reader.raise_parse_error("%s outside %s block" % (operator, {"for", "while"}))
|
|
1001
943
|
body.chunks.append(_Statement(contents, line))
|
|
1002
944
|
continue
|
|
1003
945
|
|
|
1004
|
-
|
|
1005
|
-
reader.raise_parse_error("unknown operator: %r" % operator)
|
|
946
|
+
reader.raise_parse_error("unknown operator: %r" % operator)
|
|
1006
947
|
|
|
1007
948
|
|
|
1008
949
|
VALID_CUSTOM_FUNCTION_NAMES = {
|
|
@@ -1119,6 +1060,10 @@ def check_valid_code(code):
|
|
|
1119
1060
|
Traceback (most recent call last):
|
|
1120
1061
|
...
|
|
1121
1062
|
tinybird.tornado_template.SecurityException: Invalid expression type: Attribute(value=Name(id='len', ctx=Load()), attr='__self__', ctx=Load())
|
|
1063
|
+
>>> check_valid_code('''String(__builtins__)''')
|
|
1064
|
+
Traceback (most recent call last):
|
|
1065
|
+
...
|
|
1066
|
+
tinybird.tornado_template.SecurityException: Invalid name: __builtins__
|
|
1122
1067
|
"""
|
|
1123
1068
|
body = ast.parse(code).body
|
|
1124
1069
|
for expr in body:
|
|
@@ -1142,6 +1087,11 @@ def check_valid_expr(expr):
|
|
|
1142
1087
|
raise SecurityException(f"Invalid function: {ast.dump(expr)}")
|
|
1143
1088
|
for subexpr in expr.args:
|
|
1144
1089
|
check_valid_expr(subexpr)
|
|
1090
|
+
# Keyword values never went through the allowlist and customers call functions it doesn't list
|
|
1091
|
+
# there, so they only get the checks that block reaching interpreter internals
|
|
1092
|
+
for keyword in expr.keywords:
|
|
1093
|
+
for node in ast.walk(keyword.value):
|
|
1094
|
+
_check_node_is_safe(node)
|
|
1145
1095
|
elif isinstance(expr, ast.Constant):
|
|
1146
1096
|
if isinstance(expr.value, int):
|
|
1147
1097
|
return
|
|
@@ -1153,7 +1103,9 @@ def check_valid_expr(expr):
|
|
|
1153
1103
|
return
|
|
1154
1104
|
raise SecurityException(f"Invalid Constant: {ast.dump(expr)}")
|
|
1155
1105
|
elif isinstance(expr, ast.Name):
|
|
1156
|
-
|
|
1106
|
+
# Same rule as control blocks: dunder names such as __builtins__ only reach interpreter internals
|
|
1107
|
+
if _is_dunder(expr.id):
|
|
1108
|
+
raise SecurityException(f"Invalid name: {expr.id}")
|
|
1157
1109
|
elif isinstance(expr, ast.UnaryOp):
|
|
1158
1110
|
if not isinstance(expr.op, ast.USub) and not isinstance(expr.op, ast.Not):
|
|
1159
1111
|
raise SecurityException(f"Invalid UnaryOp: {ast.dump(expr.op)}")
|
|
@@ -1173,6 +1125,8 @@ def check_valid_expr(expr):
|
|
|
1173
1125
|
check_valid_expr(expr.slice.lower)
|
|
1174
1126
|
if expr.slice.upper is not None:
|
|
1175
1127
|
check_valid_expr(expr.slice.upper)
|
|
1128
|
+
if expr.slice.step is not None:
|
|
1129
|
+
check_valid_expr(expr.slice.step)
|
|
1176
1130
|
elif isinstance(expr.slice, (ast.Constant, ast.Subscript)):
|
|
1177
1131
|
check_valid_expr(expr.slice)
|
|
1178
1132
|
else:
|
|
@@ -1190,9 +1144,199 @@ def check_valid_expr(expr):
|
|
|
1190
1144
|
check_valid_expr(x)
|
|
1191
1145
|
elif isinstance(expr, ast.FormattedValue):
|
|
1192
1146
|
check_valid_expr(expr.value)
|
|
1147
|
+
# f"{x:{expr}}" nests expressions in the format spec, which JoinedStr.values doesn't include
|
|
1148
|
+
if expr.format_spec is not None:
|
|
1149
|
+
check_valid_expr(expr.format_spec)
|
|
1193
1150
|
elif isinstance(expr, ast.IfExp):
|
|
1194
1151
|
check_valid_expr(expr.test)
|
|
1195
1152
|
check_valid_expr(expr.body)
|
|
1196
1153
|
check_valid_expr(expr.orelse)
|
|
1197
1154
|
else:
|
|
1198
1155
|
raise SecurityException(f"Invalid expression type: {ast.dump(expr)}")
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
# Names that allow evaluating code, reflection or reading attributes by string. They are not part of
|
|
1159
|
+
# SAFE_BUILTINS, so they can only resolve to template parameters (customers use `type`, `dir`, `property`
|
|
1160
|
+
# or `object` as parameter names). Reading them is allowed; calling them is rejected while parsing to give
|
|
1161
|
+
# a clear error instead of a NameError or "'str' object is not callable".
|
|
1162
|
+
UNSAFE_NAMES = frozenset(
|
|
1163
|
+
{
|
|
1164
|
+
"eval",
|
|
1165
|
+
"exec",
|
|
1166
|
+
"compile",
|
|
1167
|
+
"getattr",
|
|
1168
|
+
"setattr",
|
|
1169
|
+
"delattr",
|
|
1170
|
+
"globals",
|
|
1171
|
+
"locals",
|
|
1172
|
+
"vars",
|
|
1173
|
+
"dir",
|
|
1174
|
+
"breakpoint",
|
|
1175
|
+
"help",
|
|
1176
|
+
"memoryview",
|
|
1177
|
+
"type",
|
|
1178
|
+
"object",
|
|
1179
|
+
"super",
|
|
1180
|
+
"classmethod",
|
|
1181
|
+
"staticmethod",
|
|
1182
|
+
"property",
|
|
1183
|
+
"exit",
|
|
1184
|
+
"quit",
|
|
1185
|
+
}
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
# Attributes that reach interpreter internals without a leading underscore (frames and code objects of
|
|
1189
|
+
# generators, coroutines and tracebacks) or that read arbitrary attributes through a format string
|
|
1190
|
+
# ("{0.__class__}".format(x)). Any attribute starting with "_" is rejected too. Same idea as jinja2.sandbox.
|
|
1191
|
+
UNSAFE_ATTRIBUTE_NAMES = frozenset(
|
|
1192
|
+
{
|
|
1193
|
+
"format",
|
|
1194
|
+
"format_map",
|
|
1195
|
+
"mro",
|
|
1196
|
+
"gi_frame",
|
|
1197
|
+
"gi_code",
|
|
1198
|
+
"gi_yieldfrom",
|
|
1199
|
+
"cr_frame",
|
|
1200
|
+
"cr_code",
|
|
1201
|
+
"cr_await",
|
|
1202
|
+
"cr_origin",
|
|
1203
|
+
"ag_frame",
|
|
1204
|
+
"ag_code",
|
|
1205
|
+
"ag_await",
|
|
1206
|
+
"f_globals",
|
|
1207
|
+
"f_locals",
|
|
1208
|
+
"f_builtins",
|
|
1209
|
+
"f_back",
|
|
1210
|
+
"f_code",
|
|
1211
|
+
"tb_frame",
|
|
1212
|
+
"tb_next",
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
# Builtins exposed to compiled templates. Without an explicit "__builtins__" in the exec namespace
|
|
1217
|
+
# Python injects the real builtins module, so anything that slips through validation could reach them.
|
|
1218
|
+
# Private names are dropped because __loader__ and __spec__ can load the builtins module back. It is
|
|
1219
|
+
# read-only because the same mapping is shared by every render in the process.
|
|
1220
|
+
SAFE_BUILTINS = MappingProxyType(
|
|
1221
|
+
{
|
|
1222
|
+
name: value
|
|
1223
|
+
for name, value in vars(builtins).items()
|
|
1224
|
+
if not name.startswith("_")
|
|
1225
|
+
and name not in UNSAFE_NAMES
|
|
1226
|
+
# site's interactive helpers: license() pages its text through input()
|
|
1227
|
+
and name not in ("open", "input", "print", "copyright", "credits", "license")
|
|
1228
|
+
}
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
# Number of statements (including the "pass" bodies we add) each control block must parse to. Anything
|
|
1232
|
+
# else means the block smuggled extra statements, e.g. "{% if x: pass\nimport os\nif y %}" or "a = 1; b = 2".
|
|
1233
|
+
_CONTROL_BLOCK_SOURCES = {
|
|
1234
|
+
"if": ("{statement}:\n pass", 2),
|
|
1235
|
+
"while": ("{statement}:\n pass", 2),
|
|
1236
|
+
"for": ("{statement}:\n pass", 2),
|
|
1237
|
+
"elif": ("if 0:\n pass\n{statement}:\n pass", 4),
|
|
1238
|
+
"except": ("try:\n pass\n{statement}:\n pass", 3),
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
def _is_dunder(name: str) -> bool:
|
|
1243
|
+
return len(name) > 4 and name.startswith("__") and name.endswith("__")
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
def _check_node_is_safe(node: ast.AST) -> None:
|
|
1247
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in UNSAFE_NAMES:
|
|
1248
|
+
raise SecurityException(f"Invalid function name: {node.func.id}")
|
|
1249
|
+
if isinstance(node, ast.Attribute):
|
|
1250
|
+
if node.attr.startswith("_") or node.attr in UNSAFE_ATTRIBUTE_NAMES:
|
|
1251
|
+
raise SecurityException(f"Invalid attribute: {node.attr}")
|
|
1252
|
+
elif isinstance(node, ast.Name):
|
|
1253
|
+
if _is_dunder(node.id):
|
|
1254
|
+
raise SecurityException(f"Invalid name: {node.id}")
|
|
1255
|
+
elif isinstance(node, ast.ExceptHandler):
|
|
1256
|
+
if node.name and _is_dunder(node.name):
|
|
1257
|
+
raise SecurityException(f"Invalid name: {node.name}")
|
|
1258
|
+
elif isinstance(node, (ast.Yield, ast.YieldFrom, ast.Await)):
|
|
1259
|
+
raise SecurityException(f"Invalid expression type: {ast.dump(node)}")
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
def _check_valid_statement_tree(tree: ast.Module, expected_statements: int, statement: str) -> None:
|
|
1263
|
+
statements = 0
|
|
1264
|
+
for node in ast.walk(tree):
|
|
1265
|
+
if isinstance(node, ast.stmt):
|
|
1266
|
+
statements += 1
|
|
1267
|
+
_check_node_is_safe(node)
|
|
1268
|
+
if statements != expected_statements:
|
|
1269
|
+
raise SecurityException(f"Invalid control block: {statement}")
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def check_valid_control_statement(statement: str) -> None:
|
|
1273
|
+
"""Validate the Python code a {% if/elif/for/while/try/else/except/finally %} block emits verbatim.
|
|
1274
|
+
|
|
1275
|
+
Control blocks accept a wider set of expressions than {{ }} (comparisons, boolean operators, any
|
|
1276
|
+
method), so instead of an allowlist they reject what gives access to the interpreter: dunder names
|
|
1277
|
+
and attributes, frame/code attributes, eval-like builtins and extra smuggled statements.
|
|
1278
|
+
|
|
1279
|
+
>>> check_valid_control_statement("if defined(x) and x.lower() in ('a', 'b')")
|
|
1280
|
+
>>> check_valid_control_statement("for i, item in enumerate(split_to_array(x, ''))")
|
|
1281
|
+
>>> check_valid_control_statement("except Exception as e")
|
|
1282
|
+
>>> check_valid_control_statement("if ().__class__.__base__.__subclasses__()")
|
|
1283
|
+
Traceback (most recent call last):
|
|
1284
|
+
...
|
|
1285
|
+
tinybird.tornado_template.SecurityException: Invalid attribute: __subclasses__
|
|
1286
|
+
>>> check_valid_control_statement("elif eval('1')")
|
|
1287
|
+
Traceback (most recent call last):
|
|
1288
|
+
...
|
|
1289
|
+
tinybird.tornado_template.SecurityException: Invalid function name: eval
|
|
1290
|
+
>>> check_valid_control_statement("if defined(property) and object != 'x'")
|
|
1291
|
+
>>> check_valid_control_statement("for x in (i for i in [1]).gi_frame.f_builtins")
|
|
1292
|
+
Traceback (most recent call last):
|
|
1293
|
+
...
|
|
1294
|
+
tinybird.tornado_template.SecurityException: Invalid attribute: f_builtins
|
|
1295
|
+
>>> check_valid_control_statement("else if 1")
|
|
1296
|
+
Traceback (most recent call last):
|
|
1297
|
+
...
|
|
1298
|
+
tinybird.tornado_template.SecurityException: Invalid control block: else if 1
|
|
1299
|
+
"""
|
|
1300
|
+
operator = statement.partition(" ")[0]
|
|
1301
|
+
if operator in ("try", "else", "finally"):
|
|
1302
|
+
if statement != operator:
|
|
1303
|
+
raise SecurityException(f"Invalid control block: {statement}")
|
|
1304
|
+
return
|
|
1305
|
+
if operator not in _CONTROL_BLOCK_SOURCES:
|
|
1306
|
+
raise SecurityException(f"Invalid control block: {statement}")
|
|
1307
|
+
source, expected_statements = _CONTROL_BLOCK_SOURCES[operator]
|
|
1308
|
+
try:
|
|
1309
|
+
tree = ast.parse(source.format(statement=statement))
|
|
1310
|
+
except SyntaxError:
|
|
1311
|
+
# Compiling the generated template fails too, so nothing gets executed. Let that error surface
|
|
1312
|
+
# since its message includes the template line number users rely on.
|
|
1313
|
+
return
|
|
1314
|
+
_check_valid_statement_tree(tree, expected_statements, statement)
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
def check_valid_set_statement(statement: str) -> None:
|
|
1318
|
+
"""Validate the Python code a {% set %} block emits verbatim (``statement`` excludes "set").
|
|
1319
|
+
|
|
1320
|
+
>>> check_valid_set_statement("total = a + b * c")
|
|
1321
|
+
>>> check_valid_set_statement("csv = ','.join(test)")
|
|
1322
|
+
>>> check_valid_set_statement("x = ().__class__")
|
|
1323
|
+
Traceback (most recent call last):
|
|
1324
|
+
...
|
|
1325
|
+
tinybird.tornado_template.SecurityException: Invalid attribute: __class__
|
|
1326
|
+
>>> check_valid_set_statement("x = 1; y = 2")
|
|
1327
|
+
Traceback (most recent call last):
|
|
1328
|
+
...
|
|
1329
|
+
tinybird.tornado_template.SecurityException: Invalid set statement: x = 1; y = 2
|
|
1330
|
+
>>> check_valid_set_statement("import os")
|
|
1331
|
+
Traceback (most recent call last):
|
|
1332
|
+
...
|
|
1333
|
+
tinybird.tornado_template.SecurityException: Invalid set statement: import os
|
|
1334
|
+
"""
|
|
1335
|
+
try:
|
|
1336
|
+
tree = ast.parse(statement)
|
|
1337
|
+
except SyntaxError:
|
|
1338
|
+
# Same as in check_valid_control_statement: compiling the template raises a better error
|
|
1339
|
+
return
|
|
1340
|
+
if len(tree.body) != 1 or not isinstance(tree.body[0], (ast.Assign, ast.AugAssign, ast.Expr)):
|
|
1341
|
+
raise SecurityException(f"Invalid set statement: {statement}")
|
|
1342
|
+
_check_valid_statement_tree(tree, 1, statement)
|
|
File without changes
|
tinybird/utils/bools.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
TRUE_VALUES = {"y", "yes", "t", "true", "on", "1"}
|
|
2
|
+
FALSE_VALUES = {"n", "no", "f", "false", "off", "0"}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_bool(value: object) -> bool:
|
|
6
|
+
normalized_value = str(value).strip().lower()
|
|
7
|
+
if normalized_value in TRUE_VALUES:
|
|
8
|
+
return True
|
|
9
|
+
if normalized_value in FALSE_VALUES:
|
|
10
|
+
return False
|
|
11
|
+
raise ValueError(f"invalid truth value {value!r}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_optional_bool(value: object, default: bool = False) -> bool:
|
|
15
|
+
if value is None or value == "":
|
|
16
|
+
return default
|
|
17
|
+
if isinstance(value, bool):
|
|
18
|
+
return value
|
|
19
|
+
return parse_bool(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird_cli
|
|
3
|
-
Version: 6.5.
|
|
3
|
+
Version: 6.5.8.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli
|
|
6
6
|
Author: Tinybird
|
|
@@ -44,6 +44,14 @@ Changelog
|
|
|
44
44
|
----------
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
6.5.7
|
|
48
|
+
***********
|
|
49
|
+
|
|
50
|
+
- `Added` ``Markdown`` is now a valid ``FORMAT`` for SQL queries (for example ``tb sql "SELECT 1 FORMAT Markdown"``), returned as a Markdown table.
|
|
51
|
+
- `Changed` ``tb datasource append`` and ``tb datasource replace`` submit remote CSV, NDJSON, and Parquet URLs through the v1 import endpoint by default.
|
|
52
|
+
- `Added` Copy pipes accept ``ON_DEMAND_COMPUTE true`` so scheduled runs use on-demand compute. Manual ``tb pipe copy run`` still requires ``--on-demand-compute``, and asks for confirmation when the local datafile does not enable it (skip it with ``--yes``).
|
|
53
|
+
- `Removed` The ``--main`` flag from ``tb branch regression-tests``. It ran the comparison against the main Branch for a custom CI workflow.
|
|
54
|
+
|
|
47
55
|
6.5.6
|
|
48
56
|
***********
|
|
49
57
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
tinybird/__cli__.py,sha256=F7AVtKOPyaRjDZePzmuvAXP6pEkgj0y6o0A2lTpF_l8,237
|
|
2
|
+
tinybird/check_pypi.py,sha256=_4NkharLyR_ELrAdit-ftqIWvOf7jZNPt3i76frlo9g,975
|
|
3
|
+
tinybird/client.py,sha256=DwU-NJxwCMqlCB3NusERk2xqR8YrmtXkb84glKbGCzE,53506
|
|
4
|
+
tinybird/config.py,sha256=noWhuwgbiuhw9ebB3UL0_8k44ODmo2neNaaI_Ah5zrQ,6806
|
|
5
|
+
tinybird/context.py,sha256=o4yvlXPkMLmdh-XJl3wpmqPAMeRRz5ScKzKlHHKn_I8,1201
|
|
6
|
+
tinybird/datafile_common.py,sha256=w_MvfS8nmLuXTZy4u_xluzJK-nN_x80soTZsuiHXNXg,239278
|
|
7
|
+
tinybird/datatypes.py,sha256=3MMHtTYE0TGjNDE1xFyZlQx3SsUMrbnRUEkxVrJyAE8,12009
|
|
8
|
+
tinybird/feedback_manager.py,sha256=voAqH_isSrFzMcs2Bc8HfOMeePzjpgRD5OWqsNTOplc,68667
|
|
9
|
+
tinybird/git_settings.py,sha256=ETrcrvnhi3I9W-cGOwILXTBBNVFm5lYbeDdgTFaxT48,3898
|
|
10
|
+
tinybird/sql.py,sha256=V807-z7tlZWMowr2ezYhsxjFWm-rWejaQ1mlr0ZXtPU,47173
|
|
11
|
+
tinybird/sql_template.py,sha256=wxVDNCd0T5vmU4m3zM4HDORr8KVj6zZNnkK0FSF5icg,134046
|
|
12
|
+
tinybird/sql_template_fmt.py,sha256=cOhhkA9h_M8II-06Qx5pt3E7IXUgzvrzfWGMtWdtkUM,10525
|
|
13
|
+
tinybird/sql_toolset.py,sha256=ZLJUkqYxrvZOeKAoiItMYDjRat4qs9IH8CenbnglF1U,29926
|
|
14
|
+
tinybird/syncasync.py,sha256=fFQ5GPPnUlTJ-J3VTw9AZCNXtp5IUiP9Q22sZXcixU4,26352
|
|
15
|
+
tinybird/tb_cli.py,sha256=q1LGAsBVVMJsjR2HK62Pu6vpVtLzNmH8wHrEVUUdVkU,744
|
|
16
|
+
tinybird/tornado_template.py,sha256=mTQMTywuqTfk9xX737mT5Fv_o-PNHJxpgTa4LF-edCE,49079
|
|
17
|
+
tinybird/ch_utils/constants.py,sha256=8q5SH-0uBpYsBzmPpYsyhy-wcqvLgK1hmPr_NsKKoi8,4349
|
|
18
|
+
tinybird/ch_utils/engine.py,sha256=UAa7lFnAN5Wu25I-XyHKh23yGSQ7Ji4M_TvxpWv8q5o,38813
|
|
19
|
+
tinybird/tb_cli_modules/auth.py,sha256=3xu8STgouOgLkqlBf9LWFg9Oto_NyuDKsUWF95-zGwI,8741
|
|
20
|
+
tinybird/tb_cli_modules/branch.py,sha256=qGwOgqk91up1IjbfvjctCGXi_fqPQ6IO_mbm_u7ekb0,38642
|
|
21
|
+
tinybird/tb_cli_modules/cicd.py,sha256=T5GBMRmxLeblNKV9XICGNA37mJVtUHx2sex463-QLsk,13845
|
|
22
|
+
tinybird/tb_cli_modules/cli.py,sha256=UfrPkkvvvviJh3UTK9l24FgwIptotAWKytQj0zBgRD4,60156
|
|
23
|
+
tinybird/tb_cli_modules/common.py,sha256=7PCdcyK1pEyDwTUjNk7-DmSaitTIqAkVx_XYC-nIqLA,76015
|
|
24
|
+
tinybird/tb_cli_modules/config.py,sha256=xF4L95Flpfg-TzGkfgpYmiiDbDjxoSWJLfYt1dCdFNQ,11164
|
|
25
|
+
tinybird/tb_cli_modules/connection.py,sha256=JO2v2xTGoiMeThzCncvxMFUHLghyyGXNF0FcAvi5ZOk,24543
|
|
26
|
+
tinybird/tb_cli_modules/datasource.py,sha256=OnhbCeJtvhrVvMRqP7owA_rCZQXz-1j1l45H_NB4ALU,36467
|
|
27
|
+
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
28
|
+
tinybird/tb_cli_modules/fmt.py,sha256=edQap4tAqWMWogSIx5zriT75naLi73XTB3NwatmcrFw,3518
|
|
29
|
+
tinybird/tb_cli_modules/job.py,sha256=BxiqChDYa_4W4tFXHIUz2bOzkx8WMFywynjiPWgDlnE,2936
|
|
30
|
+
tinybird/tb_cli_modules/pipe.py,sha256=ybcMd29IOPtFamujuGa8yKkXJpmJhI_QQCumseulxag,31901
|
|
31
|
+
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
32
|
+
tinybird/tb_cli_modules/tag.py,sha256=S5x8uDW0rsJEI5lKH2mefro7tEzcHyDNWrV_ZWNyS1E,3483
|
|
33
|
+
tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
|
|
34
|
+
tinybird/tb_cli_modules/test.py,sha256=Vf8oK96V81HdKGsT79y6MUz6oz_VrYIwTbRnzzJs4rQ,4350
|
|
35
|
+
tinybird/tb_cli_modules/token.py,sha256=Tt9ijCrlahQsU2gFKmFI8bJFNSJGhY88wAAWCLiVxCM,13789
|
|
36
|
+
tinybird/tb_cli_modules/workspace.py,sha256=AWzz4qbj7LaFL2PaZohWtKKyjCMX-LPup867eRMlWVE,12621
|
|
37
|
+
tinybird/tb_cli_modules/workspace_members.py,sha256=ksXsjd233y9-sNlz4Qb-meZbX4zn1B84e_bSm2i8rhg,8731
|
|
38
|
+
tinybird/tb_cli_modules/tinyunit/tinyunit.py,sha256=IyyTdwnmF-fgEnJD3d8ntszYSFPJ5BqL6iTHqyLilkk,11055
|
|
39
|
+
tinybird/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
tinybird/utils/bools.py,sha256=Rwuh_R6j62JVkcxKskLQao7DE2Jj6TQzsbgvtCsLHP4,587
|
|
41
|
+
tinybird_cli-6.5.8.dev0.dist-info/METADATA,sha256=STqCtqkTFw7URbtT7AXxqfvCVb8G59BawS5dyjEijTs,84811
|
|
42
|
+
tinybird_cli-6.5.8.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
43
|
+
tinybird_cli-6.5.8.dev0.dist-info/entry_points.txt,sha256=PKPKuPmA4IfJYnCFHHUiw-aAWZuBomFvwCklv1OyCjE,43
|
|
44
|
+
tinybird_cli-6.5.8.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
|
|
45
|
+
tinybird_cli-6.5.8.dev0.dist-info/RECORD,,
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
tinybird/__cli__.py,sha256=i_-xpNFaP_CRKjaWjkZKj-4VaBC1PpNyvC6YTjFn8XM,236
|
|
2
|
-
tinybird/check_pypi.py,sha256=_4NkharLyR_ELrAdit-ftqIWvOf7jZNPt3i76frlo9g,975
|
|
3
|
-
tinybird/client.py,sha256=av3w9-lKfDWe-rKFE-V05-D4Ytr90kf2BCqu6z7XXH0,52753
|
|
4
|
-
tinybird/config.py,sha256=ltL9qIfY-tnFXeivrLLi7apS_OkzEJT_jRXAJu0SRQg,7164
|
|
5
|
-
tinybird/context.py,sha256=o4yvlXPkMLmdh-XJl3wpmqPAMeRRz5ScKzKlHHKn_I8,1201
|
|
6
|
-
tinybird/datafile_common.py,sha256=q9rK4MfbfbDTus4jcYNI2Kfq8K3rlN344wM17MDVdCs,237658
|
|
7
|
-
tinybird/datatypes.py,sha256=DRScTI1-lgYjbC7u_4qxOLFleWL-vief_eBg_9WU37U,11304
|
|
8
|
-
tinybird/feedback_manager.py,sha256=2n0s6AMbjtFsELJN9pJnG5JjhM3lUueXBgF4mZ0WiCw,68318
|
|
9
|
-
tinybird/git_settings.py,sha256=mqWgeboOlOFsSo97qyv595UCR2R1QCAqT4GTawBNPBg,3935
|
|
10
|
-
tinybird/sql.py,sha256=L3tTmm62iTrf6WQdcV8mmwrCMitLFUvJ3OfDzyRTMGw,47385
|
|
11
|
-
tinybird/sql_template.py,sha256=BLX_qAmnCrRouLAcpqey32-Ctub2ZaXSvpvgyadGHvM,132236
|
|
12
|
-
tinybird/sql_template_fmt.py,sha256=Ma4qcs-2r8ZXQC4GUmrCqYz34DsnGF8k5lE2Jwnr314,10638
|
|
13
|
-
tinybird/sql_toolset.py,sha256=qNKHPGKTR4MrMHKkj1qHqmlB1kPqnbT-SozyzUBeD6s,28498
|
|
14
|
-
tinybird/syncasync.py,sha256=rIPmCvygWSFqfnlVqhZH4N9gVVTvD6DEPsfoxGizYrI,27776
|
|
15
|
-
tinybird/tb_cli.py,sha256=q1LGAsBVVMJsjR2HK62Pu6vpVtLzNmH8wHrEVUUdVkU,744
|
|
16
|
-
tinybird/tornado_template.py,sha256=8e0ipnBQI4-RO5sFokybDNwkd1CBxGqvnnyJ5kjs5XU,42060
|
|
17
|
-
tinybird/ch_utils/constants.py,sha256=yTNizMzgYNBzUc2EV3moBfdrDIggOe9hiuAgWF7sv2c,4333
|
|
18
|
-
tinybird/ch_utils/engine.py,sha256=OkR86FOOUPRpCsRxlR-kunykg-K4Daqo3uS06X1uvYQ,36887
|
|
19
|
-
tinybird/tb_cli_modules/auth.py,sha256=3xu8STgouOgLkqlBf9LWFg9Oto_NyuDKsUWF95-zGwI,8741
|
|
20
|
-
tinybird/tb_cli_modules/branch.py,sha256=vbjLxIHz5XOqfMGwjyEZ7gji8Jw3PWPKKrPXViWdiIc,39348
|
|
21
|
-
tinybird/tb_cli_modules/cicd.py,sha256=i2Mw8AbmEVNBcEPYdio7liy3PGqh1ezVFZ0OmJ9ww5o,13809
|
|
22
|
-
tinybird/tb_cli_modules/cli.py,sha256=KvgZfdYRvgKG5QAiFmlfLbVGI-xmuxbs-EdkAslqrEQ,60242
|
|
23
|
-
tinybird/tb_cli_modules/common.py,sha256=tgiEmN0dAIrYYVMO-ezwiDAS3mGghCMB0ijD-gcZEtE,79524
|
|
24
|
-
tinybird/tb_cli_modules/config.py,sha256=lukiJeihI09blGeeS0FWeqVbKWhqzZmyoaJDIL0NkyQ,11182
|
|
25
|
-
tinybird/tb_cli_modules/connection.py,sha256=JO2v2xTGoiMeThzCncvxMFUHLghyyGXNF0FcAvi5ZOk,24543
|
|
26
|
-
tinybird/tb_cli_modules/datasource.py,sha256=4tkdj17-GrZ7VbsnDQ8KUA761BmSeD3XQOOqbZjUko0,36483
|
|
27
|
-
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
28
|
-
tinybird/tb_cli_modules/fmt.py,sha256=edQap4tAqWMWogSIx5zriT75naLi73XTB3NwatmcrFw,3518
|
|
29
|
-
tinybird/tb_cli_modules/job.py,sha256=BxiqChDYa_4W4tFXHIUz2bOzkx8WMFywynjiPWgDlnE,2936
|
|
30
|
-
tinybird/tb_cli_modules/pipe.py,sha256=hIvkiGv9Zvm2KgJjFzGKjc7HQZdEmEt61yFwngXhEJ8,31672
|
|
31
|
-
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
32
|
-
tinybird/tb_cli_modules/tag.py,sha256=S5x8uDW0rsJEI5lKH2mefro7tEzcHyDNWrV_ZWNyS1E,3483
|
|
33
|
-
tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
|
|
34
|
-
tinybird/tb_cli_modules/test.py,sha256=Vf8oK96V81HdKGsT79y6MUz6oz_VrYIwTbRnzzJs4rQ,4350
|
|
35
|
-
tinybird/tb_cli_modules/token.py,sha256=JXATKTlbXohP9ZDZjlz8E4VYG6zrknKZhuz_wh1zBBc,13793
|
|
36
|
-
tinybird/tb_cli_modules/workspace.py,sha256=-GW_XZHPc5atVFUkYpXsl1G8t36jZyZ1B0UWAZYwSaA,12709
|
|
37
|
-
tinybird/tb_cli_modules/workspace_members.py,sha256=ksXsjd233y9-sNlz4Qb-meZbX4zn1B84e_bSm2i8rhg,8731
|
|
38
|
-
tinybird/tb_cli_modules/tinyunit/tinyunit.py,sha256=JrZQTb9NA-1kcL2Vm0JuDRNFdH8jcYYOM3ovJDVs28E,11063
|
|
39
|
-
tinybird_cli-6.5.7.dev0.dist-info/METADATA,sha256=yzq4mC9BBAK4Q_zIsrc0h4kpR-OZzBR8PffM31ydFvQ,84089
|
|
40
|
-
tinybird_cli-6.5.7.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
41
|
-
tinybird_cli-6.5.7.dev0.dist-info/entry_points.txt,sha256=PKPKuPmA4IfJYnCFHHUiw-aAWZuBomFvwCklv1OyCjE,43
|
|
42
|
-
tinybird_cli-6.5.7.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
|
|
43
|
-
tinybird_cli-6.5.7.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|