tinybird 4.6.22.dev0__py3-none-any.whl → 4.6.24.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/sql_template.py +54 -25
- tinybird/sql_template_fmt.py +2 -2
- tinybird/syncasync.py +0 -43
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/client.py +0 -8
- tinybird/tb/modules/common.py +3 -3
- tinybird/tb/modules/create.py +2 -4
- tinybird/tb/modules/datafile/pull.py +73 -3
- tinybird/tb/modules/datasource.py +1 -1
- tinybird/tb/modules/deployment_common.py +4 -6
- tinybird/tb/modules/exceptions.py +0 -21
- tinybird/tb_cli_modules/common.py +3 -3
- tinybird/tornado_template.py +38 -93
- {tinybird-4.6.22.dev0.dist-info → tinybird-4.6.24.dev0.dist-info}/METADATA +11 -1
- {tinybird-4.6.22.dev0.dist-info → tinybird-4.6.24.dev0.dist-info}/RECORD +18 -18
- {tinybird-4.6.22.dev0.dist-info → tinybird-4.6.24.dev0.dist-info}/WHEEL +0 -0
- {tinybird-4.6.22.dev0.dist-info → tinybird-4.6.24.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird-4.6.22.dev0.dist-info → tinybird-4.6.24.dev0.dist-info}/top_level.txt +0 -0
tinybird/sql_template.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
import builtins
|
|
3
2
|
import linecache
|
|
4
3
|
import logging
|
|
5
4
|
import re
|
|
@@ -1336,8 +1335,10 @@ sqlescape_for_string_expression = sqlescape_generator(
|
|
|
1336
1335
|
)
|
|
1337
1336
|
|
|
1338
1337
|
|
|
1339
|
-
def escape_single_quote_str(s):
|
|
1340
|
-
|
|
1338
|
+
def escape_single_quote_str(s: str) -> str:
|
|
1339
|
+
# ClickHouse string literals treat backslash as an escape char, so it must be doubled too:
|
|
1340
|
+
# otherwise a trailing backslash escapes the closing quote and the following input becomes SQL
|
|
1341
|
+
return "'" + s.replace("\\", "\\\\").replace("'", "''") + "'"
|
|
1341
1342
|
|
|
1342
1343
|
|
|
1343
1344
|
def expression_wrapper(x, name, escape_arrays: bool = False):
|
|
@@ -1385,16 +1386,16 @@ _namespace = {
|
|
|
1385
1386
|
"close": None,
|
|
1386
1387
|
"print": None,
|
|
1387
1388
|
"input": None,
|
|
1388
|
-
# Keep customer parameters named `type`/`dir` usable without restoring the unsafe builtins.
|
|
1389
|
-
"type": None,
|
|
1390
|
-
"dir": None,
|
|
1391
1389
|
}
|
|
1392
1390
|
|
|
1393
1391
|
|
|
1394
|
-
|
|
1392
|
+
# Built from the builtins templates can actually resolve: names missing from SAFE_BUILTINS (`type`, `dir`,
|
|
1393
|
+
# `property`, `object`, ...) can only be template parameters, so they must be discovered as such to get a
|
|
1394
|
+
# value (None or a test-mode placeholder) instead of raising NameError.
|
|
1395
|
+
reserved_vars = {"_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error", *SAFE_BUILTINS}
|
|
1395
1396
|
for p in DEFAULT_PARAM_NAMES: # we handle these in an specific manner
|
|
1396
1397
|
reserved_vars.discard(p) # `format` is part of builtins
|
|
1397
|
-
# Allow 'id'
|
|
1398
|
+
# Allow 'id' as a template parameter name despite being a builtin - https://gitlab.com/tinybird/analytics/-/issues/19119
|
|
1398
1399
|
reserved_vars.discard("id")
|
|
1399
1400
|
error_vars = ["error", "custom_error"]
|
|
1400
1401
|
|
|
@@ -1471,8 +1472,7 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
|
|
|
1471
1472
|
return Expression(f"-- disable {feature}\n")
|
|
1472
1473
|
|
|
1473
1474
|
namespace.update(_namespace)
|
|
1474
|
-
#
|
|
1475
|
-
# We need to do the override here because if we modify the _namespace, we would filter out parameters from the users
|
|
1475
|
+
# Default it here, not in _namespace, so it stays visible to variable extraction.
|
|
1476
1476
|
namespace.update({"id": None})
|
|
1477
1477
|
namespace.update(kwargs)
|
|
1478
1478
|
namespace.update(
|
|
@@ -1822,7 +1822,7 @@ def get_var_names(t: Template):
|
|
|
1822
1822
|
>>> [v['name'] for v in get_var_names(Template("{% for name in secret_names %}{{tb_secret(name)}}{% end %}"))]
|
|
1823
1823
|
['secret_names', 'name', 'tb_secret', 'name']
|
|
1824
1824
|
|
|
1825
|
-
Type casting with date function in elif chain
|
|
1825
|
+
Type casting with date function in elif chain:
|
|
1826
1826
|
>>> t = Template("{% if kind == 'int' %}{{Int32(val, 0)}}{% elif kind == 'date' %}{{DateTime(val)}}{% elif kind == 'diff' %}{{date_diff_in_days(val, now)}}{% end %}")
|
|
1827
1827
|
>>> [v['name'] for v in get_var_names(t)]
|
|
1828
1828
|
['kind', 'Int32', 'val', 'kind', 'DateTime', 'val', 'kind', 'date_diff_in_days', 'val', 'now']
|
|
@@ -2374,7 +2374,24 @@ def wrap_vars(t, escape_arrays: bool = False):
|
|
|
2374
2374
|
return var
|
|
2375
2375
|
|
|
2376
2376
|
|
|
2377
|
-
def
|
|
2377
|
+
def _constant_table_name(node: ast.expr) -> Optional[str]:
|
|
2378
|
+
"""Value of a constant table() argument: a literal or a concatenation of string literals."""
|
|
2379
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
|
2380
|
+
return str(node.value)
|
|
2381
|
+
return _string_literal(node)
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
def _string_literal(node: ast.expr) -> Optional[str]:
|
|
2385
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
2386
|
+
return node.value
|
|
2387
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
2388
|
+
left, right = _string_literal(node.left), _string_literal(node.right)
|
|
2389
|
+
if left is not None and right is not None:
|
|
2390
|
+
return left + right
|
|
2391
|
+
return None
|
|
2392
|
+
|
|
2393
|
+
|
|
2394
|
+
def get_used_tables_in_template(sql: str) -> List[str]:
|
|
2378
2395
|
"""
|
|
2379
2396
|
>>> get_used_tables_in_template("select * from {{table('test')}}")
|
|
2380
2397
|
['test']
|
|
@@ -2388,30 +2405,42 @@ def get_used_tables_in_template(sql):
|
|
|
2388
2405
|
[]
|
|
2389
2406
|
>>> get_used_tables_in_template("select * from {{table('my.test')}}, {{table('another.one')}}")
|
|
2390
2407
|
['my.test', 'another.one']
|
|
2408
|
+
|
|
2409
|
+
Only constant table names can be known without rendering, so anything else is skipped
|
|
2410
|
+
>>> get_used_tables_in_template("select * from {{table(tbl)}}, {{TABLE('b')}}, {{table('my.' + 'c')}}")
|
|
2411
|
+
['b', 'my.c']
|
|
2391
2412
|
"""
|
|
2413
|
+
return list(_get_used_tables_in_template_cached(sql))
|
|
2414
|
+
|
|
2415
|
+
|
|
2416
|
+
# Building the Template dominates the cost and the result only depends on the SQL, which deployments and
|
|
2417
|
+
# /v0/sql pipelines scan repeatedly
|
|
2418
|
+
@lru_cache(maxsize=2**10)
|
|
2419
|
+
def _get_used_tables_in_template_cached(sql: str) -> Tuple[str, ...]:
|
|
2392
2420
|
try:
|
|
2393
2421
|
t = Template(sql)
|
|
2394
2422
|
|
|
2395
2423
|
def _n(chunks, tables):
|
|
2396
2424
|
for x in chunks:
|
|
2397
2425
|
if type(x).__name__ == "_Expression":
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2426
|
+
# Read the names from the AST instead of executing the expression: this runs outside
|
|
2427
|
+
# generate(), without the restricted builtins, on every pipe, query and deployment.
|
|
2428
|
+
for node in ast.walk(ast.parse(x.expression)):
|
|
2429
|
+
if (
|
|
2430
|
+
isinstance(node, ast.Call)
|
|
2431
|
+
and isinstance(node.func, ast.Name)
|
|
2432
|
+
and node.func.id in ("table", "TABLE")
|
|
2433
|
+
and node.args
|
|
2434
|
+
):
|
|
2435
|
+
name = _constant_table_name(node.args[0])
|
|
2436
|
+
if name is not None:
|
|
2437
|
+
tables.append(name)
|
|
2409
2438
|
elif type(x).__name__ == "_ControlBlock":
|
|
2410
2439
|
_n(x.body.chunks, tables)
|
|
2411
2440
|
|
|
2412
|
-
tables = []
|
|
2441
|
+
tables: List[str] = []
|
|
2413
2442
|
_n(t.file.body.chunks, tables)
|
|
2414
|
-
return tables
|
|
2443
|
+
return tuple(tables)
|
|
2415
2444
|
except SecurityException as e:
|
|
2416
2445
|
raise SQLTemplateException(e)
|
|
2417
2446
|
|
tinybird/sql_template_fmt.py
CHANGED
|
@@ -214,8 +214,8 @@ class TinybirdDialect(ClickHouse):
|
|
|
214
214
|
],
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
for section in override_rules:
|
|
218
|
-
for rule in
|
|
217
|
+
for section, rules in override_rules.items():
|
|
218
|
+
for rule in rules:
|
|
219
219
|
for rr in self.RULES[section]:
|
|
220
220
|
if rr.name == rule.name:
|
|
221
221
|
self.RULES[section].remove(rr)
|
tinybird/syncasync.py
CHANGED
|
@@ -260,49 +260,6 @@ def _iscoroutinefunction_or_partial(func: Any) -> bool:
|
|
|
260
260
|
return asyncio.iscoroutinefunction(func)
|
|
261
261
|
|
|
262
262
|
|
|
263
|
-
class ThreadSensitiveContext:
|
|
264
|
-
"""Async context manager to manage context for thread sensitive mode
|
|
265
|
-
This context manager controls which thread pool executor is used when in
|
|
266
|
-
thread sensitive mode. By default, a single thread pool executor is shared
|
|
267
|
-
within a process.
|
|
268
|
-
In Python 3.7+, the ThreadSensitiveContext() context manager may be used to
|
|
269
|
-
specify a thread pool per context.
|
|
270
|
-
In Python 3.6, usage of this context manager has no effect.
|
|
271
|
-
This context manager is re-entrant, so only the outer-most call to
|
|
272
|
-
ThreadSensitiveContext will set the context.
|
|
273
|
-
"""
|
|
274
|
-
|
|
275
|
-
def __init__(self):
|
|
276
|
-
self.token = None
|
|
277
|
-
|
|
278
|
-
if contextvars:
|
|
279
|
-
|
|
280
|
-
async def __aenter__(self):
|
|
281
|
-
try:
|
|
282
|
-
SyncToAsync.thread_sensitive_context.get()
|
|
283
|
-
except LookupError:
|
|
284
|
-
self.token = SyncToAsync.thread_sensitive_context.set(self)
|
|
285
|
-
|
|
286
|
-
return self
|
|
287
|
-
|
|
288
|
-
async def __aexit__(self, exc, value, tb):
|
|
289
|
-
if not self.token:
|
|
290
|
-
return
|
|
291
|
-
|
|
292
|
-
executor = SyncToAsync.context_to_thread_executor.pop(self, None)
|
|
293
|
-
if executor:
|
|
294
|
-
executor.shutdown()
|
|
295
|
-
SyncToAsync.thread_sensitive_context.reset(self.token)
|
|
296
|
-
|
|
297
|
-
else:
|
|
298
|
-
|
|
299
|
-
async def __aenter__(self):
|
|
300
|
-
return self
|
|
301
|
-
|
|
302
|
-
async def __aexit__(self, exc, value, tb):
|
|
303
|
-
pass
|
|
304
|
-
|
|
305
|
-
|
|
306
263
|
class AsyncToSync:
|
|
307
264
|
"""
|
|
308
265
|
Utility class which turns an awaitable that only works on the thread with
|
tinybird/tb/__cli__.py
CHANGED
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/forward/commands'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '4.6.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '4.6.24.dev0'
|
|
8
|
+
__revision__ = '5269fe22'
|
tinybird/tb/client.py
CHANGED
tinybird/tb/modules/common.py
CHANGED
|
@@ -1277,11 +1277,11 @@ class ConnectionReplacements:
|
|
|
1277
1277
|
"""Maps prompt parameters to API parameters."""
|
|
1278
1278
|
|
|
1279
1279
|
api_params = {}
|
|
1280
|
-
for key in params.
|
|
1280
|
+
for key, value in params.items():
|
|
1281
1281
|
try:
|
|
1282
|
-
api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] =
|
|
1282
|
+
api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] = value
|
|
1283
1283
|
except KeyError:
|
|
1284
|
-
api_params[key] =
|
|
1284
|
+
api_params[key] = value
|
|
1285
1285
|
|
|
1286
1286
|
api_params["service"] = service
|
|
1287
1287
|
return api_params
|
tinybird/tb/modules/create.py
CHANGED
|
@@ -215,14 +215,12 @@ def create_project_structure(folder: str):
|
|
|
215
215
|
"connections →": "Connect to and ingest data from popular sources: Kafka, S3 or GCS.",
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
for x in PROJECT_PATHS_DESCRIPTIONS.
|
|
218
|
+
for x, description in PROJECT_PATHS_DESCRIPTIONS.items():
|
|
219
219
|
try:
|
|
220
220
|
path = x.split("→")[0].strip()
|
|
221
221
|
f = folder_path / path
|
|
222
222
|
f.mkdir()
|
|
223
|
-
click.echo(
|
|
224
|
-
FeedbackManager.info(message=f"./{x} ") + FeedbackManager.gray(message=PROJECT_PATHS_DESCRIPTIONS[x])
|
|
225
|
-
)
|
|
223
|
+
click.echo(FeedbackManager.info(message=f"./{x} ") + FeedbackManager.gray(message=description))
|
|
226
224
|
except FileExistsError:
|
|
227
225
|
pass
|
|
228
226
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from datetime import datetime
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Any, Optional
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
|
|
7
|
+
from tinybird.datafile.common import PipeNodeTypes
|
|
8
|
+
from tinybird.datafile.parse_pipe import parse_pipe
|
|
6
9
|
from tinybird.tb.client import AuthNoTokenException, TinyB
|
|
7
10
|
from tinybird.tb.modules.datafile.format_datasource import format_datasource
|
|
8
11
|
from tinybird.tb.modules.datafile.format_pipe import format_pipe
|
|
@@ -10,6 +13,52 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
|
10
13
|
|
|
11
14
|
PULL_EXISTING_FILE_MAX_DEPTH = 5
|
|
12
15
|
|
|
16
|
+
DEMOTABLE_NODE_TYPES = {
|
|
17
|
+
PipeNodeTypes.DATA_SINK: ("sink_pipes", "Sink"),
|
|
18
|
+
PipeNodeTypes.COPY: ("copy_pipes", "Copy"),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_created_at(value: Any) -> datetime:
|
|
23
|
+
try:
|
|
24
|
+
return datetime.fromisoformat(str(value))
|
|
25
|
+
except ValueError:
|
|
26
|
+
return datetime.min
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def pipes_over_node_type_limit(pipes: list[dict[str, Any]], node_type: str, limit: Optional[int]) -> set[str]:
|
|
30
|
+
if limit is None:
|
|
31
|
+
return set()
|
|
32
|
+
|
|
33
|
+
matching_pipes = [p for p in pipes if p.get("type") == node_type]
|
|
34
|
+
if len(matching_pipes) <= limit:
|
|
35
|
+
return set()
|
|
36
|
+
|
|
37
|
+
matching_pipes.sort(key=lambda p: _parse_created_at(p.get("created_at")))
|
|
38
|
+
return {p["name"] for p in matching_pipes[limit:]}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pipes_over_plan_limits(pipes: list[dict[str, Any]], limits: dict[str, Any]) -> dict[str, str]:
|
|
42
|
+
demoted: dict[str, str] = {}
|
|
43
|
+
for node_type, (limits_key, _label) in DEMOTABLE_NODE_TYPES.items():
|
|
44
|
+
limit = limits.get(limits_key, {}).get("max")
|
|
45
|
+
for name in pipes_over_node_type_limit(pipes, node_type, limit):
|
|
46
|
+
demoted[name] = node_type
|
|
47
|
+
return demoted
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def demote_pipe_content(name: str, content: str, node_type: str) -> str:
|
|
51
|
+
datafile = parse_pipe(name, content=content, replace_includes=False).datafile
|
|
52
|
+
demoted = False
|
|
53
|
+
for node in datafile.nodes:
|
|
54
|
+
if (node.get("type") or "").lower() == node_type:
|
|
55
|
+
node.pop("type", None)
|
|
56
|
+
demoted = True
|
|
57
|
+
|
|
58
|
+
if not demoted:
|
|
59
|
+
return content
|
|
60
|
+
return format_pipe(name, datafile=datafile)
|
|
61
|
+
|
|
13
62
|
|
|
14
63
|
def folder_pull(
|
|
15
64
|
client: TinyB,
|
|
@@ -61,6 +110,7 @@ def folder_pull(
|
|
|
61
110
|
get_resource_function: str,
|
|
62
111
|
progress_bar: bool = False,
|
|
63
112
|
fmt: bool = False,
|
|
113
|
+
demoted_pipe_types: Optional[dict[str, str]] = None,
|
|
64
114
|
) -> list[Path]:
|
|
65
115
|
def write_resource(k: dict[str, Any]) -> Optional[Path]:
|
|
66
116
|
name = f"{k['name']}.{extension}"
|
|
@@ -73,8 +123,19 @@ def folder_pull(
|
|
|
73
123
|
else:
|
|
74
124
|
resource = getattr(client, get_resource_function)(k["name"])
|
|
75
125
|
resource_to_write = resource
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
resource_type = k.get("type")
|
|
127
|
+
|
|
128
|
+
demoted_node_type = demoted_pipe_types.get(k["name"]) if demoted_pipe_types else None
|
|
129
|
+
if demoted_node_type:
|
|
130
|
+
resource_to_write = demote_pipe_content(name, resource, demoted_node_type)
|
|
131
|
+
resource_type = None
|
|
132
|
+
label = DEMOTABLE_NODE_TYPES[demoted_node_type][1]
|
|
133
|
+
warnings.append(
|
|
134
|
+
f"{label} pipe '{k['name']}' exceeds the {demoted_node_type} pipe limit of your current "
|
|
135
|
+
f"plan and was pulled as a plain pipe without its {demoted_node_type} configuration. "
|
|
136
|
+
"Upgrade your plan and reconfigure it to restore it."
|
|
137
|
+
)
|
|
138
|
+
elif fmt:
|
|
78
139
|
if extension == "datasource":
|
|
79
140
|
resource_to_write = format_datasource(name, content=resource)
|
|
80
141
|
elif extension == "pipe":
|
|
@@ -85,7 +146,7 @@ def folder_pull(
|
|
|
85
146
|
dest_folder = Path(folder) / "vendor" / k["name"].split(".", 1)[0]
|
|
86
147
|
name = f"{k['name'].split('.', 1)[1]}.{extension}"
|
|
87
148
|
|
|
88
|
-
file_folder = get_file_folder(extension,
|
|
149
|
+
file_folder = get_file_folder(extension, resource_type)
|
|
89
150
|
f = Path(dest_folder) / file_folder if file_folder is not None else Path(dest_folder)
|
|
90
151
|
|
|
91
152
|
existing_file = find_existing_resource_file(Path(dest_folder), name)
|
|
@@ -128,6 +189,14 @@ def folder_pull(
|
|
|
128
189
|
pipes = client.pipes()
|
|
129
190
|
connections = client.connections()
|
|
130
191
|
|
|
192
|
+
demoted_pipe_types: dict[str, str] = {}
|
|
193
|
+
if not only_vendored:
|
|
194
|
+
try:
|
|
195
|
+
limits = client.workspace_info().get("limits", {})
|
|
196
|
+
except Exception:
|
|
197
|
+
limits = {}
|
|
198
|
+
demoted_pipe_types = pipes_over_plan_limits(pipes, limits)
|
|
199
|
+
|
|
131
200
|
if only_vendored:
|
|
132
201
|
written_files = write_files(
|
|
133
202
|
resources=[ds for ds in datasources if "shared_from" in ds],
|
|
@@ -154,6 +223,7 @@ def folder_pull(
|
|
|
154
223
|
get_resource_function="pipe_file",
|
|
155
224
|
progress_bar=progress_bar,
|
|
156
225
|
fmt=fmt,
|
|
226
|
+
demoted_pipe_types=demoted_pipe_types,
|
|
157
227
|
)
|
|
158
228
|
)
|
|
159
229
|
written_files.extend(
|
|
@@ -677,7 +677,7 @@ def datasource_start(ctx: Context, datasource_name: str) -> None:
|
|
|
677
677
|
|
|
678
678
|
@datasource.command(name="delete")
|
|
679
679
|
@click.argument("datasource_name")
|
|
680
|
-
@click.option("--sql-condition",
|
|
680
|
+
@click.option("--sql-condition", help="SQL WHERE condition to remove rows", hidden=True, required=True)
|
|
681
681
|
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
682
682
|
@click.option(
|
|
683
683
|
"--wait/--no-wait",
|
|
@@ -335,13 +335,12 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool, request_f
|
|
|
335
335
|
click.echo(FeedbackManager.error(message="Only one deployment found. Cannot discard the only deployment."))
|
|
336
336
|
return
|
|
337
337
|
|
|
338
|
-
|
|
338
|
+
live_deployment = next((deployment for deployment in deployments if deployment.get("live")), deployments[0])
|
|
339
|
+
deployment_to_discard = next(deployment for deployment in deployments if deployment is not live_deployment)
|
|
339
340
|
|
|
340
|
-
|
|
341
|
-
# staging and live, but the current CLI commands don't allow getting in that state
|
|
342
|
-
if current_deployment.get("status") != "data_ready":
|
|
341
|
+
if live_deployment.get("status") != "data_ready":
|
|
343
342
|
click.echo(FeedbackManager.error(message="Previous deployment is not ready"))
|
|
344
|
-
deploy_errors =
|
|
343
|
+
deploy_errors = live_deployment.get("errors", [])
|
|
345
344
|
for deploy_error in deploy_errors:
|
|
346
345
|
click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
|
|
347
346
|
return
|
|
@@ -359,7 +358,6 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool, request_f
|
|
|
359
358
|
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
360
359
|
sys_exit("deployment_error", result.get("error", "Unknown error"))
|
|
361
360
|
|
|
362
|
-
deployment_to_discard = deployments[1]
|
|
363
361
|
click.echo(FeedbackManager.success(message="Discard process successfully started"))
|
|
364
362
|
|
|
365
363
|
if wait:
|
|
@@ -92,20 +92,6 @@ class CLITokenException(CLIException):
|
|
|
92
92
|
super().__init__(message, "token_error", **kw_telemetry_event_data)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
class CLIBuildException(CLIException):
|
|
96
|
-
"""Exceptions generated by the build commands"""
|
|
97
|
-
|
|
98
|
-
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
99
|
-
super().__init__(message, "build_error", **kw_telemetry_event_data)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class CLIDeploymentException(CLIException):
|
|
103
|
-
"""Exceptions generated by the deployment commands"""
|
|
104
|
-
|
|
105
|
-
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
106
|
-
super().__init__(message, "deployment_error", **kw_telemetry_event_data)
|
|
107
|
-
|
|
108
|
-
|
|
109
95
|
class CLITestException(CLIException):
|
|
110
96
|
"""Exceptions generated by the test commands"""
|
|
111
97
|
|
|
@@ -127,13 +113,6 @@ class CLIMockException(CLIException):
|
|
|
127
113
|
super().__init__(message, "mock_error", **kw_telemetry_event_data)
|
|
128
114
|
|
|
129
115
|
|
|
130
|
-
class CLIAgentException(CLIException):
|
|
131
|
-
"""Exceptions generated by the agent commands"""
|
|
132
|
-
|
|
133
|
-
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
134
|
-
super().__init__(message, "agent_error", **kw_telemetry_event_data)
|
|
135
|
-
|
|
136
|
-
|
|
137
116
|
class CLILoginException(CLIException):
|
|
138
117
|
"""Exceptions generated by the login commands"""
|
|
139
118
|
|
|
@@ -1304,11 +1304,11 @@ class ConnectionReplacements:
|
|
|
1304
1304
|
"""Maps prompt parameters to API parameters."""
|
|
1305
1305
|
|
|
1306
1306
|
api_params = {}
|
|
1307
|
-
for key in params.
|
|
1307
|
+
for key, value in params.items():
|
|
1308
1308
|
try:
|
|
1309
|
-
api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] =
|
|
1309
|
+
api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] = value
|
|
1310
1310
|
except KeyError:
|
|
1311
|
-
api_params[key] =
|
|
1311
|
+
api_params[key] = value
|
|
1312
1312
|
|
|
1313
1313
|
api_params["service"] = service
|
|
1314
1314
|
return api_params
|
tinybird/tornado_template.py
CHANGED
|
@@ -200,7 +200,6 @@ import builtins
|
|
|
200
200
|
import datetime
|
|
201
201
|
import linecache
|
|
202
202
|
import os.path
|
|
203
|
-
import posixpath
|
|
204
203
|
import re
|
|
205
204
|
import threading
|
|
206
205
|
from io import StringIO
|
|
@@ -465,28 +464,6 @@ class Loader(BaseLoader):
|
|
|
465
464
|
return Template(f.read(), name=name, loader=self)
|
|
466
465
|
|
|
467
466
|
|
|
468
|
-
class DictLoader(BaseLoader):
|
|
469
|
-
"""A template loader that loads from a dictionary."""
|
|
470
|
-
|
|
471
|
-
def __init__(self, dict, **kwargs):
|
|
472
|
-
super().__init__(**kwargs)
|
|
473
|
-
self.dict = dict
|
|
474
|
-
|
|
475
|
-
def resolve_path(self, name, parent_path=None):
|
|
476
|
-
if (
|
|
477
|
-
parent_path
|
|
478
|
-
and not parent_path.startswith("<")
|
|
479
|
-
and not parent_path.startswith("/")
|
|
480
|
-
and not name.startswith("/")
|
|
481
|
-
):
|
|
482
|
-
file_dir = posixpath.dirname(parent_path)
|
|
483
|
-
name = posixpath.normpath(posixpath.join(file_dir, name))
|
|
484
|
-
return name
|
|
485
|
-
|
|
486
|
-
def _create_template(self, name):
|
|
487
|
-
return Template(self.dict[name], name=name, loader=self)
|
|
488
|
-
|
|
489
|
-
|
|
490
467
|
class _Node:
|
|
491
468
|
def each_child(self):
|
|
492
469
|
return ()
|
|
@@ -554,43 +531,6 @@ class _ExtendsBlock(_Node):
|
|
|
554
531
|
self.name = name
|
|
555
532
|
|
|
556
533
|
|
|
557
|
-
class _IncludeBlock(_Node):
|
|
558
|
-
def __init__(self, name, reader, line):
|
|
559
|
-
self.name = name
|
|
560
|
-
self.template_name = reader.name
|
|
561
|
-
self.line = line
|
|
562
|
-
|
|
563
|
-
def find_named_blocks(self, loader, named_blocks):
|
|
564
|
-
included = loader.load(self.name, self.template_name)
|
|
565
|
-
included.file.find_named_blocks(loader, named_blocks)
|
|
566
|
-
|
|
567
|
-
def generate(self, writer):
|
|
568
|
-
included = writer.loader.load(self.name, self.template_name)
|
|
569
|
-
with writer.include(included, self.line):
|
|
570
|
-
included.file.body.generate(writer)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
class _ApplyBlock(_Node):
|
|
574
|
-
def __init__(self, method, line, body=None):
|
|
575
|
-
self.method = method
|
|
576
|
-
self.line = line
|
|
577
|
-
self.body = body
|
|
578
|
-
|
|
579
|
-
def each_child(self):
|
|
580
|
-
return (self.body,)
|
|
581
|
-
|
|
582
|
-
def generate(self, writer):
|
|
583
|
-
method_name = "_tt_apply%d" % writer.apply_counter
|
|
584
|
-
writer.apply_counter += 1
|
|
585
|
-
writer.write_line("def %s():" % method_name, self.line)
|
|
586
|
-
with writer.indent():
|
|
587
|
-
writer.write_line("_tt_buffer = []", self.line)
|
|
588
|
-
writer.write_line("_tt_append = _tt_buffer.append", self.line)
|
|
589
|
-
self.body.generate(writer)
|
|
590
|
-
writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
|
|
591
|
-
writer.write_line("_tt_append(_tt_utf8(%s(%s())))" % (self.method, method_name), self.line)
|
|
592
|
-
|
|
593
|
-
|
|
594
534
|
class _ControlBlock(_Node):
|
|
595
535
|
def __init__(self, statement, line, body=None):
|
|
596
536
|
if not disable_template_security_validation.get(False):
|
|
@@ -654,11 +594,6 @@ class _Expression(_Node):
|
|
|
654
594
|
writer.write_line("_tt_append(_tt_tmp)", self.line)
|
|
655
595
|
|
|
656
596
|
|
|
657
|
-
class _Module(_Expression):
|
|
658
|
-
def __init__(self, expression, line):
|
|
659
|
-
super().__init__("_tt_modules." + expression, line, raw=True)
|
|
660
|
-
|
|
661
|
-
|
|
662
597
|
class _Text(_Node):
|
|
663
598
|
def __init__(self, value, line, whitespace):
|
|
664
599
|
self.value = value
|
|
@@ -1125,6 +1060,10 @@ def check_valid_code(code):
|
|
|
1125
1060
|
Traceback (most recent call last):
|
|
1126
1061
|
...
|
|
1127
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__
|
|
1128
1067
|
"""
|
|
1129
1068
|
body = ast.parse(code).body
|
|
1130
1069
|
for expr in body:
|
|
@@ -1148,9 +1087,11 @@ def check_valid_expr(expr):
|
|
|
1148
1087
|
raise SecurityException(f"Invalid function: {ast.dump(expr)}")
|
|
1149
1088
|
for subexpr in expr.args:
|
|
1150
1089
|
check_valid_expr(subexpr)
|
|
1151
|
-
# Keyword values
|
|
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
|
|
1152
1092
|
for keyword in expr.keywords:
|
|
1153
|
-
|
|
1093
|
+
for node in ast.walk(keyword.value):
|
|
1094
|
+
_check_node_is_safe(node)
|
|
1154
1095
|
elif isinstance(expr, ast.Constant):
|
|
1155
1096
|
if isinstance(expr.value, int):
|
|
1156
1097
|
return
|
|
@@ -1162,7 +1103,9 @@ def check_valid_expr(expr):
|
|
|
1162
1103
|
return
|
|
1163
1104
|
raise SecurityException(f"Invalid Constant: {ast.dump(expr)}")
|
|
1164
1105
|
elif isinstance(expr, ast.Name):
|
|
1165
|
-
|
|
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}")
|
|
1166
1109
|
elif isinstance(expr, ast.UnaryOp):
|
|
1167
1110
|
if not isinstance(expr.op, ast.USub) and not isinstance(expr.op, ast.Not):
|
|
1168
1111
|
raise SecurityException(f"Invalid UnaryOp: {ast.dump(expr.op)}")
|
|
@@ -1182,6 +1125,8 @@ def check_valid_expr(expr):
|
|
|
1182
1125
|
check_valid_expr(expr.slice.lower)
|
|
1183
1126
|
if expr.slice.upper is not None:
|
|
1184
1127
|
check_valid_expr(expr.slice.upper)
|
|
1128
|
+
if expr.slice.step is not None:
|
|
1129
|
+
check_valid_expr(expr.slice.step)
|
|
1185
1130
|
elif isinstance(expr.slice, (ast.Constant, ast.Subscript)):
|
|
1186
1131
|
check_valid_expr(expr.slice)
|
|
1187
1132
|
else:
|
|
@@ -1199,6 +1144,9 @@ def check_valid_expr(expr):
|
|
|
1199
1144
|
check_valid_expr(x)
|
|
1200
1145
|
elif isinstance(expr, ast.FormattedValue):
|
|
1201
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)
|
|
1202
1150
|
elif isinstance(expr, ast.IfExp):
|
|
1203
1151
|
check_valid_expr(expr.test)
|
|
1204
1152
|
check_valid_expr(expr.body)
|
|
@@ -1208,7 +1156,9 @@ def check_valid_expr(expr):
|
|
|
1208
1156
|
|
|
1209
1157
|
|
|
1210
1158
|
# Names that allow evaluating code, reflection or reading attributes by string. They are not part of
|
|
1211
|
-
# SAFE_BUILTINS
|
|
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".
|
|
1212
1162
|
UNSAFE_NAMES = frozenset(
|
|
1213
1163
|
{
|
|
1214
1164
|
"eval",
|
|
@@ -1235,11 +1185,6 @@ UNSAFE_NAMES = frozenset(
|
|
|
1235
1185
|
}
|
|
1236
1186
|
)
|
|
1237
1187
|
|
|
1238
|
-
# Common customer parameter names that collide with unsafe builtins (e.g. a `dir` sort-direction
|
|
1239
|
-
# parameter, or a `type` filter). Reads are allowed so `defined(dir)` keeps working; only calling the
|
|
1240
|
-
# builtin itself (`dir(...)`) is rejected.
|
|
1241
|
-
NAMES_ALLOWED_AS_PARAMETERS = frozenset({"type", "dir"})
|
|
1242
|
-
|
|
1243
1188
|
# Attributes that reach interpreter internals without a leading underscore (frames and code objects of
|
|
1244
1189
|
# generators, coroutines and tracebacks) or that read arbitrary attributes through a format string
|
|
1245
1190
|
# ("{0.__class__}".format(x)). Any attribute starting with "_" is rejected too. Same idea as jinja2.sandbox.
|
|
@@ -1298,29 +1243,28 @@ def _is_dunder(name: str) -> bool:
|
|
|
1298
1243
|
return len(name) > 4 and name.startswith("__") and name.endswith("__")
|
|
1299
1244
|
|
|
1300
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
|
+
|
|
1301
1262
|
def _check_valid_statement_tree(tree: ast.Module, expected_statements: int, statement: str) -> None:
|
|
1302
1263
|
statements = 0
|
|
1303
1264
|
for node in ast.walk(tree):
|
|
1304
1265
|
if isinstance(node, ast.stmt):
|
|
1305
1266
|
statements += 1
|
|
1306
|
-
|
|
1307
|
-
elif (
|
|
1308
|
-
isinstance(node, ast.Call)
|
|
1309
|
-
and isinstance(node.func, ast.Name)
|
|
1310
|
-
and node.func.id in NAMES_ALLOWED_AS_PARAMETERS
|
|
1311
|
-
):
|
|
1312
|
-
raise SecurityException(f"Invalid function name: {node.func.id}")
|
|
1313
|
-
elif isinstance(node, ast.Attribute):
|
|
1314
|
-
if node.attr.startswith("_") or node.attr in UNSAFE_ATTRIBUTE_NAMES:
|
|
1315
|
-
raise SecurityException(f"Invalid attribute: {node.attr}")
|
|
1316
|
-
elif isinstance(node, ast.Name):
|
|
1317
|
-
if _is_dunder(node.id) or (node.id in UNSAFE_NAMES and node.id not in NAMES_ALLOWED_AS_PARAMETERS):
|
|
1318
|
-
raise SecurityException(f"Invalid name: {node.id}")
|
|
1319
|
-
elif isinstance(node, ast.ExceptHandler):
|
|
1320
|
-
if node.name and _is_dunder(node.name):
|
|
1321
|
-
raise SecurityException(f"Invalid name: {node.name}")
|
|
1322
|
-
elif isinstance(node, (ast.Yield, ast.YieldFrom, ast.Await)):
|
|
1323
|
-
raise SecurityException(f"Invalid expression type: {ast.dump(node)}")
|
|
1267
|
+
_check_node_is_safe(node)
|
|
1324
1268
|
if statements != expected_statements:
|
|
1325
1269
|
raise SecurityException(f"Invalid control block: {statement}")
|
|
1326
1270
|
|
|
@@ -1342,7 +1286,8 @@ def check_valid_control_statement(statement: str) -> None:
|
|
|
1342
1286
|
>>> check_valid_control_statement("elif eval('1')")
|
|
1343
1287
|
Traceback (most recent call last):
|
|
1344
1288
|
...
|
|
1345
|
-
tinybird.tornado_template.SecurityException: Invalid name: eval
|
|
1289
|
+
tinybird.tornado_template.SecurityException: Invalid function name: eval
|
|
1290
|
+
>>> check_valid_control_statement("if defined(property) and object != 'x'")
|
|
1346
1291
|
>>> check_valid_control_statement("for x in (i for i in [1]).gi_frame.f_builtins")
|
|
1347
1292
|
Traceback (most recent call last):
|
|
1348
1293
|
...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.24.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -52,6 +52,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
52
52
|
Changelog
|
|
53
53
|
----------
|
|
54
54
|
|
|
55
|
+
4.6.22
|
|
56
|
+
********
|
|
57
|
+
|
|
58
|
+
- `Added` ``tb pull`` demotes sink and copy pipes that exceed the workspace's sink/copy pipe plan limit to plain pipes instead of failing, stripping their ``TYPE``/``EXPORT_*``/``TARGET_DATASOURCE``/``COPY_SCHEDULE`` settings and warning that the plan must be upgraded to restore them.
|
|
59
|
+
|
|
60
|
+
4.6.21
|
|
61
|
+
********
|
|
62
|
+
|
|
63
|
+
- `Fixed` ``tb deployment discard`` now discards the deployment that is not live, instead of assuming the live deployment is listed first. Previously, if removing the old deployment failed after a promotion, the command refused to discard it.
|
|
64
|
+
|
|
55
65
|
4.6.20
|
|
56
66
|
********
|
|
57
67
|
|
|
@@ -5,11 +5,11 @@ tinybird/git_settings.py,sha256=ETrcrvnhi3I9W-cGOwILXTBBNVFm5lYbeDdgTFaxT48,3898
|
|
|
5
5
|
tinybird/prompts.py,sha256=FNYpphDYlrXpm5AQM3yfQPbj4gd_TVnwoaoeswtx0OE,32699
|
|
6
6
|
tinybird/service_datasources.py,sha256=kNTlnSV6dngANVVDhFQsY77mlD5jKExy_YRC4Cw5rsQ,57555
|
|
7
7
|
tinybird/sql.py,sha256=V807-z7tlZWMowr2ezYhsxjFWm-rWejaQ1mlr0ZXtPU,47173
|
|
8
|
-
tinybird/sql_template.py,sha256=
|
|
9
|
-
tinybird/sql_template_fmt.py,sha256=
|
|
8
|
+
tinybird/sql_template.py,sha256=T9dcuB6vdYnw9BvpptIqo4MsWXbzn3O_uS6PoH0xmAM,133695
|
|
9
|
+
tinybird/sql_template_fmt.py,sha256=cOhhkA9h_M8II-06Qx5pt3E7IXUgzvrzfWGMtWdtkUM,10525
|
|
10
10
|
tinybird/sql_toolset.py,sha256=gB6ZkQww8pU-7Zu2H1l7GHttPHkgqGhYBt6MfuhduDc,29926
|
|
11
|
-
tinybird/syncasync.py,sha256=
|
|
12
|
-
tinybird/tornado_template.py,sha256=
|
|
11
|
+
tinybird/syncasync.py,sha256=fFQ5GPPnUlTJ-J3VTw9AZCNXtp5IUiP9Q22sZXcixU4,26352
|
|
12
|
+
tinybird/tornado_template.py,sha256=mTQMTywuqTfk9xX737mT5Fv_o-PNHJxpgTa4LF-edCE,49079
|
|
13
13
|
tinybird/ch_utils/constants.py,sha256=8q5SH-0uBpYsBzmPpYsyhy-wcqvLgK1hmPr_NsKKoi8,4349
|
|
14
14
|
tinybird/ch_utils/engine.py,sha256=XxBpukaupup8QEKWB7CPZu8c3r1NQuliA2ao85oWw8Y,37883
|
|
15
15
|
tinybird/datafile/common.py,sha256=QZlhZhzRuVV1UAMQRzyogPjxA6YzgD1SGwrQdEJO6QE,112481
|
|
@@ -19,30 +19,30 @@ tinybird/datafile/parse_datasource.py,sha256=yd58HrUF4yNJXLn6OsvKGpZJpvrcjLGAeJG
|
|
|
19
19
|
tinybird/datafile/parse_pipe.py,sha256=StMwF0Y1ny94hQxgPgcyx6-Jr46gCYBuPGpHF4bFiSQ,4956
|
|
20
20
|
tinybird/iterating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
tinybird/iterating/data_branch_modes.py,sha256=5YuDa-gr8mKwmES8Xro6TRKbQtaOtIw4GbC606Qhu3o,184
|
|
22
|
-
tinybird/tb/__cli__.py,sha256=
|
|
22
|
+
tinybird/tb/__cli__.py,sha256=cSOtgXlDSfy7RTi_BRGXUDdv5zrVJUtpKIQ8hGf8tro,247
|
|
23
23
|
tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
|
|
24
24
|
tinybird/tb/cli.py,sha256=IjiGfNIpxSxi1odK1kMj9s8lEhx3sAUgGA263XdmyR0,1119
|
|
25
|
-
tinybird/tb/client.py,sha256=
|
|
25
|
+
tinybird/tb/client.py,sha256=gWlh7v_tB7n2VC-sNjVQqMByWIqaR9dLYRJe7pzpb-k,59866
|
|
26
26
|
tinybird/tb/config.py,sha256=l6NtYvSPZDVOtt1W4G4ExXXDIf0hm6lvRnRoDMbdrsY,5429
|
|
27
27
|
tinybird/tb/modules/branch.py,sha256=U50nj2kZllCkOHQfJWQ-YQ260pvlcssgStMSovmqMiU,10157
|
|
28
28
|
tinybird/tb/modules/build.py,sha256=q7-aVM7q1tApiax_y6vYSCZq4cJ9ATSAm9D-elpNp7I,6543
|
|
29
29
|
tinybird/tb/modules/build_common.py,sha256=Ks_fUbS376x6KaSihUl8MMvAk4pXYIMZauVL_SOpVEA,25533
|
|
30
30
|
tinybird/tb/modules/cicd.py,sha256=xKa7XBQ1wgkYeG9lBLJQcwM7P18cY0c_QeF-KFO9_HI,8287
|
|
31
31
|
tinybird/tb/modules/cli.py,sha256=6leKHypUjieVS9DbEXizFmUZq2lbJFlBDXYyvy_v8G4,44230
|
|
32
|
-
tinybird/tb/modules/common.py,sha256=
|
|
32
|
+
tinybird/tb/modules/common.py,sha256=pRWBwL2O8SgdGTdd8kcAPTSG9mEFXgzZGXyQZMi72HY,91037
|
|
33
33
|
tinybird/tb/modules/config.py,sha256=U13HYGCILZjtWeP-LG1g63GDGR0CbqT0QE_cBZeNeOs,11157
|
|
34
34
|
tinybird/tb/modules/connection.py,sha256=HwHn0YgwqcKEYCLr2OqDzJzAUFd65Wv8MZiSUw_TLII,18452
|
|
35
35
|
tinybird/tb/modules/connection_dynamodb.py,sha256=vaB28d6t8nOd6kcUQGMUkl3IZin5DYSBN2TBPm2968Q,9490
|
|
36
36
|
tinybird/tb/modules/connection_kafka.py,sha256=AwGRrHqkV8mb5xSsFEEZ1HuamOPRukFRr5HsUJLfnbE,33502
|
|
37
37
|
tinybird/tb/modules/connection_s3.py,sha256=PeXZ4NHKhvAz0pUpXLPc7V1A8s9fllbyZJdyUFIC5Pw,16393
|
|
38
38
|
tinybird/tb/modules/copy.py,sha256=Rh4R38KLsYnNlSLIFLExqjP3J6Oy4SRJktWKOOmC8fA,7440
|
|
39
|
-
tinybird/tb/modules/create.py,sha256=
|
|
40
|
-
tinybird/tb/modules/datasource.py,sha256=
|
|
39
|
+
tinybird/tb/modules/create.py,sha256=K4rzbQEtMyu2wqoTEHzYoBSGkMzxOfPSjgxKn0EY8io,22835
|
|
40
|
+
tinybird/tb/modules/datasource.py,sha256=bxmZGaoAyuk6ev9SfqJPn8kOjyk5g1RQwwaStoz5dT0,79985
|
|
41
41
|
tinybird/tb/modules/deployment.py,sha256=vB4QSMsN4-DH-yDfSESQ5KKXgzC1t1fdCsqJ9lh8ej0,20956
|
|
42
|
-
tinybird/tb/modules/deployment_common.py,sha256=
|
|
42
|
+
tinybird/tb/modules/deployment_common.py,sha256=kqBGO1Psdb9eP8fxKuO7z6_rWOc4DFvO0Ra35NFP-20,36030
|
|
43
43
|
tinybird/tb/modules/deprecations.py,sha256=XXekrzPO9v12F1ToQDUGzLYJJ2wrEUlGKOkLCSdfHiM,4935
|
|
44
44
|
tinybird/tb/modules/endpoint.py,sha256=yRTh6rQFJ98LA0hSC8rPD3EcDaJj41gk9oCsgcZPu_c,12112
|
|
45
|
-
tinybird/tb/modules/exceptions.py,sha256=
|
|
45
|
+
tinybird/tb/modules/exceptions.py,sha256=vCEaVa13BhKRSBeICVYwazdaL8VSPIDD55oTs68h-e0,5196
|
|
46
46
|
tinybird/tb/modules/feedback_manager.py,sha256=B6iGI15HN2IQiJJ5J5FrwrAWsfBwPXsY6MYlTopMEgA,78236
|
|
47
47
|
tinybird/tb/modules/fmt.py,sha256=ejQC1-2mK42saA2R9DA-CENYgu06SUoiCoX4bCtRXT8,3734
|
|
48
48
|
tinybird/tb/modules/info.py,sha256=oO2DpPT-hpQ2Gf_TBAJHQ_xIfc1WCBpiGWhiIeM_K4w,13042
|
|
@@ -91,18 +91,18 @@ tinybird/tb/modules/datafile/format_datasource.py,sha256=_w5Y4tgl3Sejf0T8146F_ow
|
|
|
91
91
|
tinybird/tb/modules/datafile/format_pipe.py,sha256=Y4XBrJtOlQyDiRTHAfu7jo66Ud52TayNtJ55eevOb8g,7938
|
|
92
92
|
tinybird/tb/modules/datafile/pipe_checker.py,sha256=jpHfaql5sjYPKb9jSYMwV0pWSVncTZIokijKZAgWweY,24929
|
|
93
93
|
tinybird/tb/modules/datafile/playground.py,sha256=LeQj8udkolUZIrSGlwiB0WKk-x4eAyWt15HusV4hxRg,41035
|
|
94
|
-
tinybird/tb/modules/datafile/pull.py,sha256=
|
|
94
|
+
tinybird/tb/modules/datafile/pull.py,sha256=OwYsa11uT7s1ALJL6EuF_gOppHR9O8Fr7kh4azVbFsM,9262
|
|
95
95
|
tinybird/tb/modules/tinyunit/tinyunit.py,sha256=JZhIgyVv-S1H_gu0Tuv1U7U4OzFfVM_r7twzPM_JKeU,11047
|
|
96
96
|
tinybird/tb_cli_modules/cicd.py,sha256=T5GBMRmxLeblNKV9XICGNA37mJVtUHx2sex463-QLsk,13845
|
|
97
|
-
tinybird/tb_cli_modules/common.py,sha256=
|
|
97
|
+
tinybird/tb_cli_modules/common.py,sha256=7PCdcyK1pEyDwTUjNk7-DmSaitTIqAkVx_XYC-nIqLA,76015
|
|
98
98
|
tinybird/tb_cli_modules/config.py,sha256=xF4L95Flpfg-TzGkfgpYmiiDbDjxoSWJLfYt1dCdFNQ,11164
|
|
99
99
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
100
100
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
101
101
|
tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
|
|
102
102
|
tinybird/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
103
|
tinybird/utils/bools.py,sha256=Rwuh_R6j62JVkcxKskLQao7DE2Jj6TQzsbgvtCsLHP4,587
|
|
104
|
-
tinybird-4.6.
|
|
105
|
-
tinybird-4.6.
|
|
106
|
-
tinybird-4.6.
|
|
107
|
-
tinybird-4.6.
|
|
108
|
-
tinybird-4.6.
|
|
104
|
+
tinybird-4.6.24.dev0.dist-info/METADATA,sha256=kXHsAaCfmemek9Dj_XvDXDfgPZdb5Vy1fmf6qUcaBUY,18845
|
|
105
|
+
tinybird-4.6.24.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
106
|
+
tinybird-4.6.24.dev0.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
107
|
+
tinybird-4.6.24.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
|
|
108
|
+
tinybird-4.6.24.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|