tinybird 0.0.1.dev228__tar.gz → 0.0.1.dev313__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-0.0.1.dev228 → tinybird-0.0.1.dev313}/PKG-INFO +10 -5
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/ch_utils/constants.py +8 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/ch_utils/engine.py +3 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/client.py +3 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/config.py +0 -6
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/connectors.py +1 -7
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/context.py +3 -3
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/common.py +322 -23
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_pipe.py +2 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/feedback_manager.py +6 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/prompts.py +7 -4
- tinybird-0.0.1.dev313/tinybird/service_datasources.py +1085 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql.py +31 -23
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_template.py +51 -10
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_toolset.py +6 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/__cli__.py +2 -2
- tinybird-0.0.1.dev313/tinybird/tb/check_pypi.py +19 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/cli.py +2 -6
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/client.py +327 -357
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/config.py +28 -5
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/__init__.py +3 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/agent.py +916 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/animations.py +105 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/banner.py +87 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/command_agent.py +75 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/compactor.py +311 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/explore_agent.py +101 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/file_agent.py +64 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/memory.py +113 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/mock_agent.py +210 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/models.py +65 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/prompts.py +1113 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/testing_agent.py +72 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/__init__.py +0 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/analyze.py +91 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/append.py +176 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/build.py +21 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/datafile.py +273 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/deploy.py +50 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/deploy_check.py +29 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/diff_resource.py +49 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/execute_query.py +215 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/file.py +82 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/get_endpoint_stats.py +63 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/get_openapi_definition.py +66 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/mock.py +136 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/plan.py +86 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/request_endpoint.py +93 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/run_command.py +55 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/secret.py +113 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/test.py +256 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/agent/utils.py +858 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/build.py +240 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/build_common.py +506 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/cicd.py +2 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/cli.py +211 -52
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/common.py +313 -152
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/config.py +2 -4
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/connection.py +64 -105
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/copy.py +7 -9
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/create.py +87 -168
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build.py +63 -74
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_common.py +9 -9
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_datasource.py +44 -48
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_pipe.py +11 -13
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/diff.py +13 -13
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_datasource.py +5 -5
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_pipe.py +8 -8
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/playground.py +97 -106
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/pull.py +64 -54
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datasource.py +432 -316
- tinybird-0.0.1.dev313/tinybird/tb/modules/deployment.py +490 -0
- tinybird-0.0.1.dev228/tinybird/tb/modules/deployment.py → tinybird-0.0.1.dev313/tinybird/tb/modules/deployment_common.py +105 -368
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/dev_server.py +17 -3
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/endpoint.py +14 -19
- tinybird-0.0.1.dev313/tinybird/tb/modules/environment.py +152 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/exceptions.py +21 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/feedback_manager.py +12 -10
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/info.py +40 -22
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/infra.py +47 -53
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/job.py +7 -10
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/llm.py +6 -2
- tinybird-0.0.1.dev313/tinybird/tb/modules/local.py +303 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/local_common.py +791 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/local_logs.py +196 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/login.py +57 -0
- tinybird-0.0.1.dev228/tinybird/tb/modules/login.py → tinybird-0.0.1.dev313/tinybird/tb/modules/login_common.py +40 -54
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/materialization.py +16 -10
- tinybird-0.0.1.dev313/tinybird/tb/modules/mock.py +41 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/mock_common.py +17 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/open.py +1 -3
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/pipe.py +2 -4
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/project.py +63 -1
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/secret.py +22 -64
- tinybird-0.0.1.dev313/tinybird/tb/modules/secret_common.py +52 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/shell.py +8 -21
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/sink.py +6 -8
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/telemetry.py +4 -0
- tinybird-0.0.1.dev313/tinybird/tb/modules/test.py +65 -0
- tinybird-0.0.1.dev228/tinybird/tb/modules/test.py → tinybird-0.0.1.dev313/tinybird/tb/modules/test_common.py +86 -147
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/tinyunit/tinyunit.py +3 -17
- {tinybird-0.0.1.dev228/tinybird/tb_cli_modules → tinybird-0.0.1.dev313/tinybird/tb/modules}/tinyunit/tinyunit_lib.py +0 -6
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/token.py +48 -35
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/watch.py +8 -10
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/workspace.py +28 -45
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/workspace_members.py +16 -23
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/cli.py +19 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/common.py +2 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/fmt.py +4 -2
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/pipe.py +15 -1
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -14
- {tinybird-0.0.1.dev228/tinybird/tb/modules → tinybird-0.0.1.dev313/tinybird/tb_cli_modules}/tinyunit/tinyunit_lib.py +0 -6
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tornado_template.py +6 -7
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/PKG-INFO +10 -5
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/SOURCES.txt +41 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/requires.txt +9 -4
- tinybird-0.0.1.dev228/tinybird/tb/check_pypi.py +0 -24
- tinybird-0.0.1.dev228/tinybird/tb/modules/build.py +0 -512
- tinybird-0.0.1.dev228/tinybird/tb/modules/local.py +0 -179
- tinybird-0.0.1.dev228/tinybird/tb/modules/local_common.py +0 -440
- tinybird-0.0.1.dev228/tinybird/tb/modules/mock.py +0 -157
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/setup.cfg +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/__cli__.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/check_pypi.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/exceptions.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_connection.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_datasource.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datatypes.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/git_settings.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/syncasync.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/deprecations.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/table.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev313
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -8,20 +8,24 @@ Author-email: support@tinybird.co
|
|
|
8
8
|
Requires-Python: >=3.9, <3.14
|
|
9
9
|
Description-Content-Type: text/x-rst
|
|
10
10
|
Requires-Dist: aiofiles==24.1.0
|
|
11
|
-
Requires-Dist: anthropic==0.
|
|
11
|
+
Requires-Dist: anthropic==0.55.0
|
|
12
12
|
Requires-Dist: boto3
|
|
13
13
|
Requires-Dist: click<8.2,>=8.1.6
|
|
14
14
|
Requires-Dist: clickhouse-toolset==0.34.dev0
|
|
15
15
|
Requires-Dist: colorama==0.4.6
|
|
16
|
-
Requires-Dist: confluent-kafka==2.8.
|
|
16
|
+
Requires-Dist: confluent-kafka==2.8.2
|
|
17
17
|
Requires-Dist: cryptography~=41.0.0
|
|
18
18
|
Requires-Dist: croniter==1.3.15
|
|
19
19
|
Requires-Dist: docker==7.1.0
|
|
20
20
|
Requires-Dist: GitPython~=3.1.32
|
|
21
21
|
Requires-Dist: humanfriendly~=8.2
|
|
22
|
+
Requires-Dist: plotext==5.3.2
|
|
22
23
|
Requires-Dist: prompt_toolkit==3.0.48
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: logfire-api==4.2.0
|
|
25
|
+
Requires-Dist: pydantic~=2.11.7
|
|
26
|
+
Requires-Dist: pydantic-ai-slim[anthropic]~=0.5.0
|
|
27
|
+
Requires-Dist: pydantic-ai-slim[retries]~=0.5.0
|
|
28
|
+
Requires-Dist: pyperclip==1.9.0
|
|
25
29
|
Requires-Dist: pyyaml<6.1,>=6.0
|
|
26
30
|
Requires-Dist: requests<3,>=2.28.1
|
|
27
31
|
Requires-Dist: shandy-sqlfmt==0.11.1
|
|
@@ -35,6 +39,7 @@ Requires-Dist: packaging<24,>=23.1
|
|
|
35
39
|
Requires-Dist: llm>=0.19
|
|
36
40
|
Requires-Dist: thefuzz==0.22.1
|
|
37
41
|
Requires-Dist: python-dotenv==1.1.0
|
|
42
|
+
Requires-Dist: pyjwt[crypto]==2.9.0
|
|
38
43
|
Dynamic: author
|
|
39
44
|
Dynamic: author-email
|
|
40
45
|
Dynamic: description
|
|
@@ -255,4 +255,12 @@ VALID_QUERY_FORMATS = (
|
|
|
255
255
|
"JSONStrings",
|
|
256
256
|
"Prometheus",
|
|
257
257
|
"Native",
|
|
258
|
+
"RowBinaryWithNamesAndTypes",
|
|
259
|
+
"TabSeparated",
|
|
260
|
+
"JSONCompactEachRowWithNamesAndTypes",
|
|
261
|
+
"TabSeparatedWithNamesAndTypes",
|
|
262
|
+
"JSONCompactEachRow",
|
|
263
|
+
"JSONCompact",
|
|
264
|
+
"JSONStringsEachRowWithProgress",
|
|
265
|
+
"ODBCDriver2",
|
|
258
266
|
)
|
|
@@ -134,8 +134,9 @@ class TableDetails:
|
|
|
134
134
|
_version = self.details.get("version", None)
|
|
135
135
|
return _version
|
|
136
136
|
|
|
137
|
-
def is_replicated(self):
|
|
138
|
-
|
|
137
|
+
def is_replicated(self) -> bool:
|
|
138
|
+
engine: Optional[str] = self.details.get("engine", None)
|
|
139
|
+
return engine is not None and "Replicated" in engine
|
|
139
140
|
|
|
140
141
|
def is_mergetree_family(self) -> bool:
|
|
141
142
|
return self.engine is not None and "mergetree" in self.engine.lower()
|
|
@@ -569,6 +569,7 @@ class TinyB:
|
|
|
569
569
|
populate_condition: Optional[str] = None,
|
|
570
570
|
truncate: bool = True,
|
|
571
571
|
unlink_on_populate_error: bool = False,
|
|
572
|
+
on_demand_compute: bool = False,
|
|
572
573
|
):
|
|
573
574
|
params: Dict[str, Any] = {
|
|
574
575
|
"truncate": "true" if truncate else "false",
|
|
@@ -578,6 +579,8 @@ class TinyB:
|
|
|
578
579
|
params.update({"populate_subset": populate_subset})
|
|
579
580
|
if populate_condition:
|
|
580
581
|
params.update({"populate_condition": populate_condition})
|
|
582
|
+
if on_demand_compute:
|
|
583
|
+
params.update({"on_demand_compute": "true"})
|
|
581
584
|
response = await self._req(
|
|
582
585
|
f"/v0/pipes/{pipe_name}/nodes/{node_name}/population?{urlencode(params)}", method="POST"
|
|
583
586
|
)
|
|
@@ -38,13 +38,10 @@ LEGACY_HOSTS = {
|
|
|
38
38
|
"https://api.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
|
|
39
39
|
"https://api.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
|
|
40
40
|
"https://api.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
|
|
41
|
-
"https://api.wadus6.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus6",
|
|
42
41
|
"https://api.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
|
|
43
42
|
"https://api.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
|
|
44
43
|
"https://api.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
|
|
45
44
|
"https://api.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
|
|
46
|
-
"https://api.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
|
|
47
|
-
"https://api.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
|
|
48
45
|
"https://ui.tinybird.co": "https://app.tinybird.co/gcp/europe-west3",
|
|
49
46
|
"https://ui.us-east.tinybird.co": "https://app.tinybird.co/gcp/us-east4",
|
|
50
47
|
"https://ui.us-east.aws.tinybird.co": "https://app.tinybird.co/aws/us-east-1",
|
|
@@ -62,13 +59,10 @@ LEGACY_HOSTS = {
|
|
|
62
59
|
"https://ui.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
|
|
63
60
|
"https://ui.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
|
|
64
61
|
"https://ui.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
|
|
65
|
-
"https://ui.wadus6.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus6",
|
|
66
62
|
"https://ui.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
|
|
67
63
|
"https://ui.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
|
|
68
64
|
"https://ui.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
|
|
69
65
|
"https://ui.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
|
|
70
|
-
"https://ui.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
|
|
71
|
-
"https://ui.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
|
|
@@ -369,13 +369,7 @@ class Snowflake(Connector):
|
|
|
369
369
|
the_type = "String"
|
|
370
370
|
if t.startswith("NUMBER"):
|
|
371
371
|
the_type = "Int32"
|
|
372
|
-
if (
|
|
373
|
-
t.startswith("FLOAT")
|
|
374
|
-
or t.startswith("DOUBLE")
|
|
375
|
-
or t.startswith("REAL")
|
|
376
|
-
or t.startswith("NUMERIC")
|
|
377
|
-
or t.startswith("DECIMAL")
|
|
378
|
-
):
|
|
372
|
+
if t.startswith(("FLOAT", "DOUBLE", "REAL", "NUMERIC", "DECIMAL")):
|
|
379
373
|
the_type = "Float32"
|
|
380
374
|
if t == "DATE":
|
|
381
375
|
the_type = "Date"
|
|
@@ -3,15 +3,15 @@ from typing import TYPE_CHECKING
|
|
|
3
3
|
|
|
4
4
|
# Avoid circular import error
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from
|
|
6
|
+
from hfi.hfi_workspace_data import HfiWorkspaceData
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
workspace_id: ContextVar[str] = ContextVar("workspace_id")
|
|
9
|
-
|
|
10
|
+
hfi_workspace_data: ContextVar["HfiWorkspaceData"] = ContextVar("hfi_workspace_data")
|
|
10
11
|
table_id: ContextVar[str] = ContextVar("table_id")
|
|
11
12
|
hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
|
|
12
13
|
hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
|
|
13
14
|
use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
|
|
14
|
-
allow_gatherer_fallback: ContextVar[bool] = ContextVar("allow_gatherer_fallback")
|
|
15
15
|
gatherer_allow_s3_backup_on_user_errors: ContextVar[bool] = ContextVar("gatherer_allow_s3_backup_on_user_errors")
|
|
16
16
|
disable_template_security_validation: ContextVar[bool] = ContextVar("disable_template_security_validation")
|
|
17
17
|
origin: ContextVar[str] = ContextVar("origin")
|
|
@@ -122,7 +122,12 @@ VALID_PIPE_NODE_TYPES = {
|
|
|
122
122
|
PipeNodeTypes.STREAM,
|
|
123
123
|
PipeNodeTypes.DATA_SINK,
|
|
124
124
|
}
|
|
125
|
-
VISIBLE_PIPE_NODE_TYPES = {
|
|
125
|
+
VISIBLE_PIPE_NODE_TYPES = {
|
|
126
|
+
PipeNodeTypes.MATERIALIZED,
|
|
127
|
+
PipeNodeTypes.COPY,
|
|
128
|
+
PipeNodeTypes.ENDPOINT,
|
|
129
|
+
PipeNodeTypes.DATA_SINK,
|
|
130
|
+
}
|
|
126
131
|
|
|
127
132
|
|
|
128
133
|
class DataFileExtensions:
|
|
@@ -179,7 +184,23 @@ class CopyParameters(Parameters):
|
|
|
179
184
|
|
|
180
185
|
class MaterializedParameters(Parameters):
|
|
181
186
|
MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union({"datasource"})
|
|
182
|
-
ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES)
|
|
187
|
+
ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES).union({"deployment_method"})
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class SinkParameters(Parameters):
|
|
191
|
+
# For Kafka sinks
|
|
192
|
+
KAFKA_MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union(
|
|
193
|
+
{"export_connection_name", "export_kafka_topic", "export_schedule"}
|
|
194
|
+
)
|
|
195
|
+
KAFKA_ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(KAFKA_MANDATORY_ATTRIBUTES)
|
|
196
|
+
|
|
197
|
+
# For S3/GCS sinks
|
|
198
|
+
BLOB_MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union(
|
|
199
|
+
{"export_connection_name", "export_schedule", "export_bucket_uri", "export_file_template"}
|
|
200
|
+
)
|
|
201
|
+
BLOB_ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(BLOB_MANDATORY_ATTRIBUTES).union(
|
|
202
|
+
{"export_format", "export_compression", "export_write_strategy", "export_strategy"}
|
|
203
|
+
)
|
|
183
204
|
|
|
184
205
|
|
|
185
206
|
DATAFILE_NEW_LINE = "\n"
|
|
@@ -273,6 +294,13 @@ class Datafile:
|
|
|
273
294
|
def set_kind(self, kind: DatafileKind):
|
|
274
295
|
self.kind = kind
|
|
275
296
|
|
|
297
|
+
def validate_standard_node(self, node: Dict[str, Any]):
|
|
298
|
+
for key in node.keys():
|
|
299
|
+
if key not in PipeParameters.valid_params():
|
|
300
|
+
raise DatafileValidationError(
|
|
301
|
+
f"Standard node {repr(node['name'])} has an invalid attribute ({PipeParameters.canonical_name(key)})"
|
|
302
|
+
)
|
|
303
|
+
|
|
276
304
|
def validate_copy_node(self, node: Dict[str, Any]):
|
|
277
305
|
if missing := [param for param in CopyParameters.required_params() if param not in node]:
|
|
278
306
|
raise DatafileValidationError(
|
|
@@ -305,15 +333,95 @@ class Datafile:
|
|
|
305
333
|
f"Materialized node {repr(node['name'])} has an invalid attribute ({MaterializedParameters.canonical_name(key)})"
|
|
306
334
|
)
|
|
307
335
|
|
|
336
|
+
def validate_sink_node(self, node: Dict[str, Any]):
|
|
337
|
+
export_connection_name = node.get("export_connection_name")
|
|
338
|
+
|
|
339
|
+
if not export_connection_name:
|
|
340
|
+
raise DatafileValidationError(
|
|
341
|
+
f"Sink node {repr(node['name'])} is missing required parameter 'export_connection_name'"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Determine connection type to validate appropriate parameters
|
|
345
|
+
# First, try to determine from presence of Kafka-specific parameters
|
|
346
|
+
has_kafka_topic = "export_kafka_topic" in node
|
|
347
|
+
has_s3_gcs_params = any(param in node for param in ["export_bucket_uri", "export_file_template"])
|
|
348
|
+
|
|
349
|
+
# If both types of parameters are present, that's an error
|
|
350
|
+
if has_kafka_topic and has_s3_gcs_params:
|
|
351
|
+
raise DatafileValidationError(
|
|
352
|
+
f"Sink node {repr(node['name'])} has mixed Kafka and S3/GCS parameters. Use either Kafka parameters (export_kafka_topic) or S3/GCS parameters (export_bucket_uri, export_file_template), not both."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# If we have Kafka-specific parameters, treat as Kafka sink
|
|
356
|
+
if has_kafka_topic:
|
|
357
|
+
# Kafka sink validation
|
|
358
|
+
if missing := [param for param in SinkParameters.KAFKA_MANDATORY_ATTRIBUTES if param not in node]:
|
|
359
|
+
raise DatafileValidationError(
|
|
360
|
+
f"Kafka sink node {repr(node['name'])} is missing required parameters: {missing}"
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# For Kafka sinks, only specific parameters should be present
|
|
364
|
+
kafka_specific_params = (
|
|
365
|
+
{"export_kafka_topic", "export_schedule", "export_connection_name"}
|
|
366
|
+
| PipeParameters.MANDATORY_ATTRIBUTES
|
|
367
|
+
| PipeParameters.ACCEPTED_ATTRIBUTES
|
|
368
|
+
)
|
|
369
|
+
for key in node.keys():
|
|
370
|
+
if key not in kafka_specific_params:
|
|
371
|
+
raise DatafileValidationError(
|
|
372
|
+
f"Kafka sink node {repr(node['name'])} has invalid parameter '{key}'. Only export_kafka_topic, export_schedule, and export_connection_name are allowed for Kafka sinks."
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# If we have S3/GCS-specific parameters, treat as S3/GCS sink
|
|
376
|
+
elif has_s3_gcs_params:
|
|
377
|
+
# S3/GCS sink validation
|
|
378
|
+
if missing := [param for param in SinkParameters.BLOB_MANDATORY_ATTRIBUTES if param not in node]:
|
|
379
|
+
raise DatafileValidationError(
|
|
380
|
+
f"S3/GCS sink node {repr(node['name'])} is missing required parameters: {missing}"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Check that only valid parameters are present
|
|
384
|
+
for key in node.keys():
|
|
385
|
+
if key not in SinkParameters.BLOB_ACCEPTED_ATTRIBUTES:
|
|
386
|
+
raise DatafileValidationError(
|
|
387
|
+
f"S3/GCS sink node {repr(node['name'])} has invalid parameter '{key}'"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# If no type-specific parameters are present, we can't determine the type
|
|
391
|
+
# This means the sink is missing required parameters for any sink type
|
|
392
|
+
else:
|
|
393
|
+
# Check if we have any export parameters at all besides connection_name and schedule
|
|
394
|
+
export_params = {
|
|
395
|
+
k
|
|
396
|
+
for k in node.keys()
|
|
397
|
+
if k.startswith("export_") and k not in {"export_connection_name", "export_schedule"}
|
|
398
|
+
}
|
|
399
|
+
if not export_params:
|
|
400
|
+
raise DatafileValidationError(
|
|
401
|
+
f"Sink node {repr(node['name'])} is missing required parameters. "
|
|
402
|
+
f"For Kafka sinks, provide 'export_kafka_topic'. "
|
|
403
|
+
f"For S3/GCS sinks, provide 'export_bucket_uri' and 'export_file_template'."
|
|
404
|
+
)
|
|
405
|
+
else:
|
|
406
|
+
# There are some export parameters, but they don't match known patterns
|
|
407
|
+
raise DatafileValidationError(
|
|
408
|
+
f"Sink node {repr(node['name'])} has unrecognized export parameters: {export_params}. "
|
|
409
|
+
f"For Kafka sinks, use 'export_kafka_topic'. "
|
|
410
|
+
f"For S3/GCS sinks, use 'export_bucket_uri' and 'export_file_template'."
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Validate schedule format (common for both Kafka and S3/GCS)
|
|
414
|
+
export_schedule = node.get("export_schedule")
|
|
415
|
+
if export_schedule and export_schedule != ON_DEMAND and not croniter.is_valid(export_schedule):
|
|
416
|
+
raise DatafileValidationError(
|
|
417
|
+
f"Sink node {repr(node['name'])} has invalid export_schedule '{export_schedule}'. Must be @on-demand or a valid cron expression."
|
|
418
|
+
)
|
|
419
|
+
|
|
308
420
|
def validate(self):
|
|
309
421
|
if self.kind == DatafileKind.pipe:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
# [x] Materialized nodes have target datasource
|
|
314
|
-
# [x] Only one materialized node
|
|
315
|
-
# [x] Only one node of any specific type
|
|
316
|
-
# (rbarbadillo): there's a HUGE amount of validations in api_pipes.py, we should somehow merge them
|
|
422
|
+
if len(self.nodes) == 0:
|
|
423
|
+
raise DatafileValidationError("Pipe data file must have at least one node")
|
|
424
|
+
|
|
317
425
|
non_standard_nodes_count = 0
|
|
318
426
|
for node in self.nodes:
|
|
319
427
|
node_type = node.get("type", "").lower()
|
|
@@ -325,10 +433,15 @@ class Datafile:
|
|
|
325
433
|
self.validate_materialized_node(node)
|
|
326
434
|
if node_type == PipeNodeTypes.COPY:
|
|
327
435
|
self.validate_copy_node(node)
|
|
436
|
+
if node_type == PipeNodeTypes.DATA_SINK:
|
|
437
|
+
self.validate_sink_node(node)
|
|
438
|
+
if node_type in {PipeNodeTypes.STANDARD, ""}:
|
|
439
|
+
self.validate_standard_node(node)
|
|
328
440
|
if node_type not in VALID_PIPE_NODE_TYPES:
|
|
329
441
|
raise DatafileValidationError(
|
|
330
|
-
f"Invalid node
|
|
442
|
+
f"Invalid node '{repr(node['name'])}' of type ({node_type}). Allowed node types: {VISIBLE_PIPE_NODE_TYPES}"
|
|
331
443
|
)
|
|
444
|
+
|
|
332
445
|
for token in self.tokens:
|
|
333
446
|
if token["permission"].upper() != "READ":
|
|
334
447
|
raise DatafileValidationError(
|
|
@@ -354,6 +467,15 @@ class Datafile:
|
|
|
354
467
|
raise DatafileValidationError(
|
|
355
468
|
f"Invalid permission {token['permission']} for token {token['token_name']}. Only READ and APPEND are allowed for datasources"
|
|
356
469
|
)
|
|
470
|
+
|
|
471
|
+
# Validate sorting key if present
|
|
472
|
+
if "engine" in node and isinstance(node["engine"], dict) and "args" in node["engine"]:
|
|
473
|
+
for arg_name, arg_value in node["engine"]["args"]:
|
|
474
|
+
if arg_name.lower() == "sorting_key":
|
|
475
|
+
# Check for sorting key constraints
|
|
476
|
+
self._validate_sorting_key(arg_value, node)
|
|
477
|
+
break
|
|
478
|
+
|
|
357
479
|
# Validate Kafka params
|
|
358
480
|
if any(param in node for param in KAFKA_PARAMS) and (
|
|
359
481
|
missing := [param for param in REQUIRED_KAFKA_PARAMS if param not in node]
|
|
@@ -376,6 +498,151 @@ class Datafile:
|
|
|
376
498
|
# We cannot validate a datafile whose kind is unknown
|
|
377
499
|
pass
|
|
378
500
|
|
|
501
|
+
def _validate_sorting_key(self, sorting_key: str, node: Dict[str, Any]) -> None:
|
|
502
|
+
"""
|
|
503
|
+
Validates that a sorting key doesn't reference:
|
|
504
|
+
- Nullable columns
|
|
505
|
+
- AggregateFunction types
|
|
506
|
+
- Engine version columns for ReplacingMergeTree
|
|
507
|
+
"""
|
|
508
|
+
if sorting_key == "tuple()" or not sorting_key:
|
|
509
|
+
return # Empty sorting key is valid
|
|
510
|
+
|
|
511
|
+
engine_ver_column = self._extract_engine_ver_column(node)
|
|
512
|
+
schema_columns = {col["name"]: col for col in node["columns"]}
|
|
513
|
+
sorting_key_columns = self._parse_sorting_key_columns(sorting_key, engine_ver_column)
|
|
514
|
+
|
|
515
|
+
self._validate_columns_against_schema(sorting_key_columns, schema_columns)
|
|
516
|
+
|
|
517
|
+
def _extract_engine_ver_column(self, node: Dict[str, Any]) -> Optional[str]:
|
|
518
|
+
engine_info = node.get("engine", {})
|
|
519
|
+
|
|
520
|
+
if not isinstance(engine_info, dict):
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
engine_type = engine_info.get("type", "")
|
|
524
|
+
if engine_type != "ReplacingMergeTree":
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
engine_args = engine_info.get("args", [])
|
|
528
|
+
for arg_name, arg_value in engine_args:
|
|
529
|
+
if arg_name == "ver":
|
|
530
|
+
return arg_value
|
|
531
|
+
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
def _parse_sorting_key_columns(self, sorting_key: str, engine_ver_column: Optional[str]) -> List[str]:
|
|
535
|
+
"""Parse sorting key to extract column names and validate constraints."""
|
|
536
|
+
# Validate ENGINE_VER column constraint early
|
|
537
|
+
if engine_ver_column and engine_ver_column in sorting_key:
|
|
538
|
+
raise DatafileValidationError(
|
|
539
|
+
f"ENGINE_VER column '{engine_ver_column}' cannot be included in the sorting key for ReplacingMergeTree. "
|
|
540
|
+
f"Including the version column in the sorting key prevents deduplication because rows with different "
|
|
541
|
+
f"versions will have different sorting keys and won't be considered duplicates. The sorting key should "
|
|
542
|
+
f"define the record identity (what makes it unique), while ENGINE_VER tracks which version to keep."
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# Remove tuple() wrapper if present
|
|
546
|
+
column_str = sorting_key
|
|
547
|
+
if column_str.startswith("tuple(") and column_str.endswith(")"):
|
|
548
|
+
column_str = column_str[6:-1]
|
|
549
|
+
|
|
550
|
+
sorting_key_columns = []
|
|
551
|
+
|
|
552
|
+
for part in column_str.split(","):
|
|
553
|
+
part = part.strip()
|
|
554
|
+
|
|
555
|
+
if self._is_aggregate_function_expression(part):
|
|
556
|
+
raise DatafileValidationError(
|
|
557
|
+
f"Sorting key contains aggregate function expression '{part}'. Aggregate function expressions cannot be used in sorting keys."
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Extract column names from the part
|
|
561
|
+
extracted_columns = self._extract_column_names_from_part(part)
|
|
562
|
+
sorting_key_columns.extend(extracted_columns)
|
|
563
|
+
|
|
564
|
+
return sorting_key_columns
|
|
565
|
+
|
|
566
|
+
def _is_aggregate_function_expression(self, part: str) -> bool:
|
|
567
|
+
"""Check if a sorting key part is an aggregate function expression."""
|
|
568
|
+
if not ("(" in part and part.endswith(")")):
|
|
569
|
+
return False
|
|
570
|
+
|
|
571
|
+
func_start = part.find("(")
|
|
572
|
+
func_name = part[:func_start].strip().lower()
|
|
573
|
+
|
|
574
|
+
aggregate_function_names = {
|
|
575
|
+
"sum",
|
|
576
|
+
"count",
|
|
577
|
+
"avg",
|
|
578
|
+
"min",
|
|
579
|
+
"max",
|
|
580
|
+
"any",
|
|
581
|
+
"grouparray",
|
|
582
|
+
"groupuniqarray",
|
|
583
|
+
"uniq",
|
|
584
|
+
"summerge",
|
|
585
|
+
"countmerge",
|
|
586
|
+
"avgmerge",
|
|
587
|
+
"minmerge",
|
|
588
|
+
"maxmerge",
|
|
589
|
+
"anymerge",
|
|
590
|
+
"grouparraymerge",
|
|
591
|
+
"groupuniqarraymerge",
|
|
592
|
+
"uniqmerge",
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return func_name in aggregate_function_names
|
|
596
|
+
|
|
597
|
+
def _extract_column_names_from_part(self, part: str) -> List[str]:
|
|
598
|
+
"""Extract column names from a sorting key part."""
|
|
599
|
+
columns = []
|
|
600
|
+
|
|
601
|
+
if "(" in part and part.endswith(")"):
|
|
602
|
+
# Function expression - extract column names from inside parentheses
|
|
603
|
+
func_start = part.find("(")
|
|
604
|
+
inner_content = part[func_start + 1 : -1].strip()
|
|
605
|
+
for inner_part in inner_content.split(","):
|
|
606
|
+
inner_part = inner_part.strip().strip("`")
|
|
607
|
+
if inner_part and inner_part.isidentifier():
|
|
608
|
+
columns.append(inner_part)
|
|
609
|
+
elif part:
|
|
610
|
+
# Simple column name
|
|
611
|
+
column_name = part.strip("`")
|
|
612
|
+
if column_name:
|
|
613
|
+
columns.append(column_name)
|
|
614
|
+
|
|
615
|
+
return columns
|
|
616
|
+
|
|
617
|
+
def _validate_columns_against_schema(
|
|
618
|
+
self, sorting_key_columns: List[str], schema_columns: Dict[str, Dict[str, Any]]
|
|
619
|
+
) -> None:
|
|
620
|
+
"""Validate each column in the sorting key against the schema."""
|
|
621
|
+
if not schema_columns:
|
|
622
|
+
return # No schema information available, can't validate
|
|
623
|
+
|
|
624
|
+
for col_name in sorting_key_columns:
|
|
625
|
+
if col_name not in schema_columns:
|
|
626
|
+
continue
|
|
627
|
+
|
|
628
|
+
self._validate_single_column(col_name, schema_columns[col_name])
|
|
629
|
+
|
|
630
|
+
def _validate_single_column(self, col_name: str, column_info: Dict[str, Any]) -> None:
|
|
631
|
+
"""Validate a single column for use in sorting keys."""
|
|
632
|
+
col_type = column_info.get("type", "").lower()
|
|
633
|
+
|
|
634
|
+
# we need to check any presence of Nullable in the column type
|
|
635
|
+
is_nullable = column_info.get("nullable", False) or "Nullable(" in column_info.get("type", "")
|
|
636
|
+
|
|
637
|
+
if is_nullable:
|
|
638
|
+
raise DatafileValidationError(
|
|
639
|
+
f"Sorting key contains nullable column '{col_name}'. Nullable columns cannot be used in sorting keys."
|
|
640
|
+
)
|
|
641
|
+
if "aggregatefunction" in col_type:
|
|
642
|
+
raise DatafileValidationError(
|
|
643
|
+
f"Sorting key contains column '{col_name}' with AggregateFunction type. AggregateFunction columns cannot be used in sorting keys."
|
|
644
|
+
)
|
|
645
|
+
|
|
379
646
|
|
|
380
647
|
def format_filename(filename: str, hide_folders: bool = False):
|
|
381
648
|
return os.path.basename(filename) if hide_folders else filename
|
|
@@ -1265,6 +1532,8 @@ def parse(
|
|
|
1265
1532
|
default_node: Optional[str] = None,
|
|
1266
1533
|
basepath: str = ".",
|
|
1267
1534
|
replace_includes: bool = True,
|
|
1535
|
+
# TODO(eclbg): I think we could remove `skip_eval` in Forward, and pin it to False. This would let us remove some
|
|
1536
|
+
# other functions like `eval_var` that obscure things a bit.
|
|
1268
1537
|
skip_eval: bool = False,
|
|
1269
1538
|
) -> ParseResult:
|
|
1270
1539
|
lines = list(StringIO(s, newline=None))
|
|
@@ -1375,11 +1644,18 @@ def parse(
|
|
|
1375
1644
|
|
|
1376
1645
|
parser_state.current_node["indexes"] = indexes
|
|
1377
1646
|
|
|
1378
|
-
def assign_var(v: str) -> Callable[[VarArg(str), KwArg(Any)], None]:
|
|
1647
|
+
def assign_var(v: str, allowed_values: Optional[set[str]] = None) -> Callable[[VarArg(str), KwArg(Any)], None]:
|
|
1379
1648
|
@multiline_not_supported
|
|
1380
1649
|
def _f(*args: str, **kwargs: Any):
|
|
1381
1650
|
s = _unquote((" ".join(args)).strip())
|
|
1382
|
-
|
|
1651
|
+
val = eval_var(s, skip=skip_eval)
|
|
1652
|
+
if allowed_values and val.lower() not in {v.lower() for v in allowed_values}:
|
|
1653
|
+
raise DatafileSyntaxError(
|
|
1654
|
+
f"{val} is not an allowed value for {kwargs['cmd'].upper()}. Use one of: {allowed_values}",
|
|
1655
|
+
lineno=kwargs["lineno"],
|
|
1656
|
+
pos=1,
|
|
1657
|
+
)
|
|
1658
|
+
parser_state.current_node[v.lower()] = val
|
|
1383
1659
|
|
|
1384
1660
|
return _f
|
|
1385
1661
|
|
|
@@ -1474,6 +1750,10 @@ def parse(
|
|
|
1474
1750
|
else:
|
|
1475
1751
|
doc.description = description
|
|
1476
1752
|
|
|
1753
|
+
def kafka_ssl_ca_pem(*args: str, **kwargs: Any) -> None:
|
|
1754
|
+
kafka_ssl_ca_pem = ("\n".join(args)).strip()
|
|
1755
|
+
parser_state.current_node["kafka_ssl_ca_pem"] = kafka_ssl_ca_pem
|
|
1756
|
+
|
|
1477
1757
|
def sql(var_name: str, **kwargs: Any) -> Callable[[str, KwArg(Any)], None]:
|
|
1478
1758
|
# TODO(eclbg): We shouldn't allow SQL in datasource files
|
|
1479
1759
|
def _f(sql: str, *args: Any, **kwargs: Any) -> None:
|
|
@@ -1587,11 +1867,26 @@ def parse(
|
|
|
1587
1867
|
def version(*args: str, **kwargs: Any) -> None:
|
|
1588
1868
|
pass # whatever, it's deprecated
|
|
1589
1869
|
|
|
1590
|
-
@not_supported_yet()
|
|
1591
1870
|
def shared_with(*args: str, **kwargs: Any) -> None:
|
|
1871
|
+
# Count total workspaces collected
|
|
1872
|
+
total_workspaces = 0
|
|
1873
|
+
|
|
1592
1874
|
for entries in args:
|
|
1593
|
-
# In case they specify multiple workspaces
|
|
1594
|
-
|
|
1875
|
+
# In case they specify multiple workspaces, handle both line-separated and comma-separated values
|
|
1876
|
+
lines = _unquote(entries).splitlines()
|
|
1877
|
+
for line in lines:
|
|
1878
|
+
# Split by comma and strip whitespace from each workspace name
|
|
1879
|
+
workspaces = [workspace.strip().rstrip(",") for workspace in line.split(",") if workspace.strip()]
|
|
1880
|
+
doc.shared_with += workspaces
|
|
1881
|
+
total_workspaces += len(workspaces)
|
|
1882
|
+
|
|
1883
|
+
# Validate that at least one workspace was provided
|
|
1884
|
+
if total_workspaces == 0:
|
|
1885
|
+
raise DatafileSyntaxError(
|
|
1886
|
+
"SHARED_WITH requires at least one workspace name",
|
|
1887
|
+
lineno=kwargs["lineno"],
|
|
1888
|
+
pos=1,
|
|
1889
|
+
)
|
|
1595
1890
|
|
|
1596
1891
|
def __init_engine(v: str):
|
|
1597
1892
|
if not parser_state.current_node:
|
|
@@ -1608,6 +1903,8 @@ def parse(
|
|
|
1608
1903
|
def _f(*args: str, **kwargs: Any):
|
|
1609
1904
|
__init_engine(f"ENGINE_{v}".upper())
|
|
1610
1905
|
engine_arg = eval_var(_unquote((" ".join(args)).strip()), skip=skip_eval)
|
|
1906
|
+
if v.lower() == "ttl" and not engine_arg:
|
|
1907
|
+
return
|
|
1611
1908
|
parser_state.current_node["engine"]["args"].append((v, engine_arg))
|
|
1612
1909
|
|
|
1613
1910
|
return _f
|
|
@@ -1663,9 +1960,10 @@ def parse(
|
|
|
1663
1960
|
"import_query": assign_var("import_query"), # Deprecated, BQ and SFK
|
|
1664
1961
|
"import_table_arn": assign_var("import_table_arn"), # Only for DynamoDB
|
|
1665
1962
|
"import_export_bucket": assign_var("import_export_bucket"), # For DynamoDB
|
|
1666
|
-
"shared_with": shared_with,
|
|
1963
|
+
"shared_with": shared_with,
|
|
1667
1964
|
"export_service": export_service, # Deprecated
|
|
1668
1965
|
"forward_query": sql("forward_query"),
|
|
1966
|
+
"backfill": assign_var("backfill", allowed_values={"skip"}),
|
|
1669
1967
|
# ENGINE_* commands are added dynamically after this dict's definition
|
|
1670
1968
|
},
|
|
1671
1969
|
DatafileKind.pipe: {
|
|
@@ -1684,6 +1982,7 @@ def parse(
|
|
|
1684
1982
|
"include": include,
|
|
1685
1983
|
"sql": sql("sql"),
|
|
1686
1984
|
"version": version,
|
|
1985
|
+
"deployment_method": assign_var("deployment_method", allowed_values={"alter"}),
|
|
1687
1986
|
"export_connection_name": assign_var("export_connection_name"),
|
|
1688
1987
|
"export_schedule": assign_var("export_schedule"),
|
|
1689
1988
|
"export_bucket_uri": assign_var("export_bucket_uri"),
|
|
@@ -1701,9 +2000,13 @@ def parse(
|
|
|
1701
2000
|
"kafka_key": assign_var("kafka_key"),
|
|
1702
2001
|
"kafka_secret": assign_var("kafka_secret"),
|
|
1703
2002
|
"kafka_schema_registry_url": assign_var("kafka_schema_registry_url"),
|
|
1704
|
-
"kafka_ssl_ca_pem":
|
|
2003
|
+
"kafka_ssl_ca_pem": kafka_ssl_ca_pem,
|
|
1705
2004
|
"kafka_security_protocol": assign_var("kafka_security_protocol"),
|
|
1706
2005
|
"kafka_sasl_mechanism": assign_var("kafka_sasl_mechanism"),
|
|
2006
|
+
"kafka_sasl_oauthbearer_method": assign_var("kafka_sasl_oauthbearer_method"),
|
|
2007
|
+
"kafka_sasl_oauthbearer_aws_region": assign_var("kafka_sasl_oauthbearer_aws_region"),
|
|
2008
|
+
"kafka_sasl_oauthbearer_aws_role_arn": assign_var("kafka_sasl_oauthbearer_aws_role_arn"),
|
|
2009
|
+
"kafka_sasl_oauthbearer_aws_external_id": assign_var("kafka_sasl_oauthbearer_aws_external_id"),
|
|
1707
2010
|
"kafka_key_avro_deserialization": kafka_key_avro_deserialization_deprecated,
|
|
1708
2011
|
"s3_region": assign_var("s3_region"),
|
|
1709
2012
|
"s3_arn": assign_var("s3_arn"),
|
|
@@ -1748,11 +2051,7 @@ def parse(
|
|
|
1748
2051
|
lexer = list(sa)
|
|
1749
2052
|
if lexer:
|
|
1750
2053
|
cmd, args = lexer[0], lexer[1:]
|
|
1751
|
-
if (
|
|
1752
|
-
parser_state.multiline
|
|
1753
|
-
and cmd.lower() in cmds
|
|
1754
|
-
and not (line.startswith(" ") or line.startswith("\t"))
|
|
1755
|
-
):
|
|
2054
|
+
if parser_state.multiline and cmd.lower() in cmds and not line.startswith((" ", "\t")):
|
|
1756
2055
|
cmds[parser_state.command](
|
|
1757
2056
|
parser_state.multiline_string,
|
|
1758
2057
|
lineno=lineno,
|
|
@@ -2205,7 +2504,7 @@ def is_file_a_datasource(filename: str) -> bool:
|
|
|
2205
2504
|
|
|
2206
2505
|
for line in lines:
|
|
2207
2506
|
trimmed_line = line.strip().lower()
|
|
2208
|
-
if trimmed_line.startswith("schema"
|
|
2507
|
+
if trimmed_line.startswith(("schema", "engine")):
|
|
2209
2508
|
return True
|
|
2210
2509
|
|
|
2211
2510
|
return False
|
|
@@ -11,7 +11,7 @@ from tinybird.datafile.common import (
|
|
|
11
11
|
parse,
|
|
12
12
|
)
|
|
13
13
|
from tinybird.datafile.exceptions import IncludeFileNotFoundException, ParseException
|
|
14
|
-
from tinybird.sql_template import get_template_and_variables, render_sql_template
|
|
14
|
+
from tinybird.sql_template import get_template_and_variables, render_sql_template
|
|
15
15
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
16
16
|
from tinybird.tornado_template import UnClosedIfError
|
|
17
17
|
|
|
@@ -54,7 +54,7 @@ def parse_pipe(
|
|
|
54
54
|
if sql.strip()[0] == "%":
|
|
55
55
|
secrets_list: Optional[List[str]] = None
|
|
56
56
|
if secrets:
|
|
57
|
-
secrets_list =
|
|
57
|
+
secrets_list = list(secrets.keys())
|
|
58
58
|
# Setting test_mode=True to ignore errors on required parameters and
|
|
59
59
|
# secrets_in_test_mode=False to raise errors on missing secrets
|
|
60
60
|
sql, _, variable_warnings = render_sql_template(
|
|
@@ -598,6 +598,12 @@ Ready? """
|
|
|
598
598
|
warning_confirm_delete_branch = prompt_message("Do you want to remove '{branch}' Branch?")
|
|
599
599
|
warning_confirm_delete_release = prompt_message("Do you want to remove Release {semver}?")
|
|
600
600
|
warning_confirm_rollback_release = prompt_message("Do you want to rollback current Release {semver} to {rollback}?")
|
|
601
|
+
warning_confirm_on_demand_compute = warning_message(
|
|
602
|
+
"On-demand compute will incur additional costs beyond your regular usage.\n"
|
|
603
|
+
"This feature uses dedicated compute resources that are billed separately.\n"
|
|
604
|
+
"You can read more about the pricing at https://www.tinybird.co/docs/classic/work-with-data/process-and-copy/materialized-views#compute-compute-separation-for-populates\n\n"
|
|
605
|
+
"Do you want to proceed with on-demand compute?"
|
|
606
|
+
)
|
|
601
607
|
|
|
602
608
|
warning_confirm_delete_token = prompt_message("Do you want to delete Token {token}?")
|
|
603
609
|
warning_confirm_refresh_token = prompt_message("Do you want to refresh Token {token}?")
|