tinybird 4.5.12.dev0__tar.gz → 4.6.1__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-4.5.12.dev0 → tinybird-4.6.1}/PKG-INFO +12 -3
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datafile/common.py +17 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/service_datasources.py +4 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/sql.py +8 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/sql_template.py +2 -2
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/__cli__.py +2 -2
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/client.py +20 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/build_common.py +25 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/common.py +69 -29
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/connection.py +46 -0
- tinybird-4.6.1/tinybird/tb/modules/connection_dynamodb.py +240 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/connection_s3.py +2 -2
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/create.py +12 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/build_pipe.py +2 -2
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/pull.py +29 -6
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datasource.py +166 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/local_common.py +2 -2
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/project.py +10 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/query_output.py +3 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/secret.py +15 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/test_common.py +1 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/common.py +3 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/PKG-INFO +12 -3
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/SOURCES.txt +1 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/requires.txt +1 -1
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/setup.cfg +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/__cli__.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/check_pypi.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/client.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/config.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/context.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datafile/exceptions.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datafile/parse_connection.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datafile/parse_datasource.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datafile/parse_pipe.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/datatypes.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/feedback_manager.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/git_settings.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/prompts.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/sql_toolset.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/syncasync.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/check_pypi.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/cli.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/config.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/branch.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/build.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/cicd.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/cli.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/config.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/connection_kafka.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/copy.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/build.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/format_connection.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/datafile/playground.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/deployment.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/deployment_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/deprecations.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/endpoint.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/feedback_manager.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/info.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/infra.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/job.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/job_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/llm.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/local.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/local_logs.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/login.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/login_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/logs.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/materialization.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/open.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/preview.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/project_commands.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/py_project.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/secret_common.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/sink.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/table.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/test.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/token.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/ts_project.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/watch.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird/tornado_template.py +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-4.5.12.dev0 → tinybird-4.6.1}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.1
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -27,7 +27,7 @@ Requires-Dist: requests<3,>=2.28.1
|
|
|
27
27
|
Requires-Dist: shandy-sqlfmt==0.11.1
|
|
28
28
|
Requires-Dist: shandy-sqlfmt[jinjafmt]==0.11.1
|
|
29
29
|
Requires-Dist: toposort==1.10
|
|
30
|
-
Requires-Dist: tornado~=6.
|
|
30
|
+
Requires-Dist: tornado~=6.5.5
|
|
31
31
|
Requires-Dist: urllib3<2,>=1.26.14
|
|
32
32
|
Requires-Dist: watchdog==6.0.0
|
|
33
33
|
Requires-Dist: wheel
|
|
@@ -52,12 +52,21 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
52
52
|
Changelog
|
|
53
53
|
----------
|
|
54
54
|
|
|
55
|
+
4.6.1
|
|
56
|
+
*******
|
|
57
|
+
|
|
58
|
+
- `Changed` `tb pull` to overwrite existing local files when pulling from a Tinybird workspace.
|
|
59
|
+
|
|
60
|
+
4.6.0
|
|
61
|
+
*******
|
|
62
|
+
|
|
63
|
+
- `Added` Support for AWS DynamoDB datasource creation and management.
|
|
64
|
+
|
|
55
65
|
4.5.12
|
|
56
66
|
*******
|
|
57
67
|
|
|
58
68
|
- `Fixed` `tb info` now lists branches and marks the current branch correctly when running on a branch.
|
|
59
69
|
|
|
60
|
-
|
|
61
70
|
4.5.11
|
|
62
71
|
*******
|
|
63
72
|
|
|
@@ -277,6 +277,13 @@ VALID_BLOB_STORAGE_CRON_VALUES = {
|
|
|
277
277
|
"@auto",
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
REQUIRED_DYNAMODB_PARAMS = {
|
|
281
|
+
"import_connection_name",
|
|
282
|
+
"import_table_arn",
|
|
283
|
+
"import_export_bucket",
|
|
284
|
+
}
|
|
285
|
+
DYNAMODB_PARAMS = REQUIRED_DYNAMODB_PARAMS
|
|
286
|
+
|
|
280
287
|
|
|
281
288
|
def extract_column_names_from_sorting_key_part(part: str) -> List[str]:
|
|
282
289
|
"""
|
|
@@ -575,8 +582,15 @@ class Datafile:
|
|
|
575
582
|
)
|
|
576
583
|
if "kafka_group_id" in node and not str(node["kafka_group_id"]).strip():
|
|
577
584
|
raise DatafileValidationError("KAFKA_GROUP_ID cannot be empty")
|
|
585
|
+
# Validate DynamoDB params first since import_connection_name is shared with blob storage.
|
|
586
|
+
is_dynamodb = any(param in node for param in ("import_table_arn", "import_export_bucket"))
|
|
587
|
+
if is_dynamodb:
|
|
588
|
+
if missing := [param for param in REQUIRED_DYNAMODB_PARAMS if param not in node]:
|
|
589
|
+
raise DatafileValidationError(
|
|
590
|
+
f"Some DynamoDB params have been provided, but the following required ones are missing: {missing}"
|
|
591
|
+
)
|
|
578
592
|
# Validate S3 params
|
|
579
|
-
|
|
593
|
+
elif any(param in node for param in BLOB_STORAGE_PARAMS):
|
|
580
594
|
if missing := [param for param in REQUIRED_BLOB_STORAGE_PARAMS if param not in node]:
|
|
581
595
|
raise DatafileValidationError(
|
|
582
596
|
f"Some connection params have been provided, but the following required ones are missing: {missing}"
|
|
@@ -2097,6 +2111,8 @@ def parse(
|
|
|
2097
2111
|
"s3_arn": assign_var("s3_arn"),
|
|
2098
2112
|
"s3_access_key": assign_var("s3_access_key"),
|
|
2099
2113
|
"s3_secret": assign_var("s3_secret"),
|
|
2114
|
+
"dynamodb_arn": assign_var("dynamodb_arn"),
|
|
2115
|
+
"dynamodb_region": assign_var("dynamodb_region"),
|
|
2100
2116
|
"gcs_service_account_credentials_json": assign_var_json("gcs_service_account_credentials_json"),
|
|
2101
2117
|
"gcs_access_id": assign_var("gcs_hmac_access_id"),
|
|
2102
2118
|
"gcs_secret": assign_var("gcs_hmac_secret"),
|
|
@@ -219,7 +219,9 @@ def get_tinybird_service_datasources() -> List[Dict[str, Any]]:
|
|
|
219
219
|
{"name": "processed_messages", "type": "Int32"},
|
|
220
220
|
{"name": "processed_bytes", "type": "Int32"},
|
|
221
221
|
{"name": "committed_messages", "type": "Int32"},
|
|
222
|
+
{"name": "time_read", "type": "Float32"},
|
|
222
223
|
{"name": "time_process", "type": "Float32"},
|
|
224
|
+
{"name": "time_write", "type": "Float32"},
|
|
223
225
|
{"name": "msg", "type": "String"},
|
|
224
226
|
],
|
|
225
227
|
},
|
|
@@ -880,7 +882,9 @@ def get_organization_service_datasources() -> List[Dict[str, Any]]:
|
|
|
880
882
|
{"name": "processed_messages", "type": "Int32"},
|
|
881
883
|
{"name": "processed_bytes", "type": "Int32"},
|
|
882
884
|
{"name": "committed_messages", "type": "Int32"},
|
|
885
|
+
{"name": "time_read", "type": "Float32"},
|
|
883
886
|
{"name": "time_process", "type": "Float32"},
|
|
887
|
+
{"name": "time_write", "type": "Float32"},
|
|
884
888
|
{"name": "msg", "type": "String"},
|
|
885
889
|
],
|
|
886
890
|
},
|
|
@@ -398,6 +398,14 @@ def parse_table_structure(schema: str) -> List[Dict[str, Any]]:
|
|
|
398
398
|
>>> parse_table_structure('c Nullable(Float32) DEFAULT NULL')
|
|
399
399
|
[{'name': 'c', 'type': 'Float32', 'codec': None, 'default_value': None, 'jsonpath': None, 'nullable': True, 'normalized_name': 'c'}]
|
|
400
400
|
|
|
401
|
+
>>> parse_table_structure('c LowCardinality(Nullable(String))')
|
|
402
|
+
[{'name': 'c', 'type': 'LowCardinality(Nullable(String))', 'codec': None, 'default_value': None, 'jsonpath': None, 'nullable': False, 'normalized_name': 'c'}]
|
|
403
|
+
|
|
404
|
+
>>> parse_table_structure('c Nullable(LowCardinality(Nullable(String)))')
|
|
405
|
+
Traceback (most recent call last):
|
|
406
|
+
...
|
|
407
|
+
ValueError: Nested type LowCardinality(Nullable(String)) cannot be inside Nullable type
|
|
408
|
+
|
|
401
409
|
>>> parse_table_structure("c String DEFAULT 'bla'")
|
|
402
410
|
[{'name': 'c', 'type': 'String', 'codec': None, 'default_value': "DEFAULT 'bla'", 'jsonpath': None, 'nullable': False, 'normalized_name': 'c'}]
|
|
403
411
|
|
|
@@ -7,7 +7,7 @@ from collections import deque
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from functools import lru_cache
|
|
9
9
|
from json import loads
|
|
10
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
|
|
11
11
|
|
|
12
12
|
from tornado import escape
|
|
13
13
|
from tornado.util import ObjectDict, exec_in, unicode_type
|
|
@@ -1499,7 +1499,7 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
|
|
|
1499
1499
|
)
|
|
1500
1500
|
|
|
1501
1501
|
exec_in(self.compiled, namespace)
|
|
1502
|
-
execute = namespace["_tt_execute"]
|
|
1502
|
+
execute = cast(Callable[[], bytes], namespace["_tt_execute"])
|
|
1503
1503
|
# Clear the traceback module's cache of source data now that
|
|
1504
1504
|
# we've generated a new template (mainly for this module's
|
|
1505
1505
|
# unittests, where different tests reuse the same name).
|
|
@@ -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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '4.6.1'
|
|
8
|
+
__revision__ = '41da03f'
|
|
@@ -1183,6 +1183,21 @@ class TinyB:
|
|
|
1183
1183
|
except Exception:
|
|
1184
1184
|
return False
|
|
1185
1185
|
|
|
1186
|
+
def validate_dynamodb(
|
|
1187
|
+
self, table_arn: str, region: str, role_arn: str, external_id_seed: Optional[str] = None
|
|
1188
|
+
) -> Dict[str, Any]:
|
|
1189
|
+
body: Dict[str, Any] = {"table_arn": table_arn, "region": region, "role_arn": role_arn}
|
|
1190
|
+
if external_id_seed:
|
|
1191
|
+
body["external_id_seed"] = external_id_seed
|
|
1192
|
+
response = self._req_raw(
|
|
1193
|
+
"/v1/integrations/dynamodb/validate",
|
|
1194
|
+
method="POST",
|
|
1195
|
+
data=json.dumps(body),
|
|
1196
|
+
)
|
|
1197
|
+
if response.status_code >= 400:
|
|
1198
|
+
raise requests.HTTPError(parse_error_response(response), response=response)
|
|
1199
|
+
return response.json()
|
|
1200
|
+
|
|
1186
1201
|
def get_trust_policy(self, service: str, external_id_seed: Optional[str] = None) -> Dict[str, Any]:
|
|
1187
1202
|
params = {}
|
|
1188
1203
|
if external_id_seed:
|
|
@@ -1195,11 +1210,15 @@ class TinyB:
|
|
|
1195
1210
|
params["bucket"] = bucket
|
|
1196
1211
|
return self._req(f"/v0/integrations/{service}/policies/write-access-policy?{urlencode(params)}")
|
|
1197
1212
|
|
|
1198
|
-
def get_access_read_policy(
|
|
1213
|
+
def get_access_read_policy(
|
|
1214
|
+
self, service: str, bucket: Optional[str] = None, table_name: Optional[str] = None
|
|
1215
|
+
) -> Dict[str, Any]:
|
|
1199
1216
|
params = {}
|
|
1200
1217
|
if bucket:
|
|
1201
1218
|
# The Kafka endpoint scopes the policy via `msk_cluster_arn`, not `bucket`.
|
|
1202
1219
|
params["msk_cluster_arn" if service == "kafka" else "bucket"] = bucket
|
|
1220
|
+
if table_name:
|
|
1221
|
+
params["table_name"] = table_name
|
|
1203
1222
|
return self._req(f"/v0/integrations/{service}/policies/read-access-policy?{urlencode(params)}")
|
|
1204
1223
|
|
|
1205
1224
|
def sql_get_format(self, sql: str, with_clickhouse_format: bool = False) -> str:
|
|
@@ -247,6 +247,8 @@ def build_project(
|
|
|
247
247
|
if load_fixtures:
|
|
248
248
|
append_project_fixtures(project, tb_client, project_files)
|
|
249
249
|
echo_build_feedback(build.get("feedback", []))
|
|
250
|
+
if with_connections:
|
|
251
|
+
echo_dynamodb_local_backfill_feedback(build)
|
|
250
252
|
return True
|
|
251
253
|
elif build_result == "failed":
|
|
252
254
|
error = format_build_errors(result.get("errors", []))
|
|
@@ -461,6 +463,29 @@ def echo_build_feedback(feedback: list[dict[str, Any]]) -> None:
|
|
|
461
463
|
)
|
|
462
464
|
|
|
463
465
|
|
|
466
|
+
def echo_dynamodb_local_backfill_feedback(build: dict[str, Any]) -> None:
|
|
467
|
+
datasources_by_id = {datasource.get("id"): datasource.get("name") for datasource in build.get("datasources", [])}
|
|
468
|
+
for data_linker in build.get("data_linkers", []):
|
|
469
|
+
if data_linker.get("service") != "dynamodb":
|
|
470
|
+
continue
|
|
471
|
+
|
|
472
|
+
settings = data_linker.get("settings") or {}
|
|
473
|
+
export_arn = settings.get("initial_export_arn")
|
|
474
|
+
if not export_arn:
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
datasource_name = datasources_by_id.get(data_linker.get("datasource_id")) or "unknown"
|
|
478
|
+
click.echo(
|
|
479
|
+
FeedbackManager.warning(
|
|
480
|
+
message=(
|
|
481
|
+
f"△ DynamoDB initial export backfill started for datasource '{datasource_name}'. "
|
|
482
|
+
"AWS exports can stay in progress for several minutes; Tinybird Local will keep retrying "
|
|
483
|
+
f"the import until AWS marks the export as completed. Export ARN: {export_arn}."
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
464
489
|
def format_build_errors(build_errors: list[dict[str, Any]]) -> str:
|
|
465
490
|
full_error_msg = ""
|
|
466
491
|
for build_error in build_errors:
|
|
@@ -105,7 +105,9 @@ def echo_safe_humanfriendly_tables_format_smart_table(data: Iterable[Any], colum
|
|
|
105
105
|
try:
|
|
106
106
|
click.echo(humanfriendly.tables.format_smart_table(data, column_names=column_names))
|
|
107
107
|
except ValueError as exc:
|
|
108
|
-
|
|
108
|
+
# Python 3.11 wording: "max() arg is an empty sequence"
|
|
109
|
+
# Python 3.12 wording: "max() iterable argument is empty"
|
|
110
|
+
if str(exc) in ("max() arg is an empty sequence", "max() iterable argument is empty"):
|
|
109
111
|
click.echo("------------")
|
|
110
112
|
click.echo("Empty")
|
|
111
113
|
click.echo("------------")
|
|
@@ -122,7 +124,9 @@ def echo_safe_humanfriendly_tables_format_pretty_table(data: Iterable[Any], colu
|
|
|
122
124
|
try:
|
|
123
125
|
click.echo(humanfriendly.tables.format_pretty_table(data, column_names=column_names))
|
|
124
126
|
except ValueError as exc:
|
|
125
|
-
|
|
127
|
+
# Python 3.11 wording: "max() arg is an empty sequence"
|
|
128
|
+
# Python 3.12 wording: "max() iterable argument is empty"
|
|
129
|
+
if str(exc) in ("max() arg is an empty sequence", "max() iterable argument is empty"):
|
|
126
130
|
click.echo("------------")
|
|
127
131
|
click.echo("Empty")
|
|
128
132
|
click.echo("------------")
|
|
@@ -139,7 +143,9 @@ def echo_safe_format_table(data: Iterable[Any], columns) -> None:
|
|
|
139
143
|
try:
|
|
140
144
|
click.echo(format_table(data, columns))
|
|
141
145
|
except ValueError as exc:
|
|
142
|
-
|
|
146
|
+
# Python 3.11 wording: "max() arg is an empty sequence"
|
|
147
|
+
# Python 3.12 wording: "max() iterable argument is empty"
|
|
148
|
+
if str(exc) in ("max() arg is an empty sequence", "max() iterable argument is empty"):
|
|
143
149
|
click.echo("------------")
|
|
144
150
|
click.echo("Empty")
|
|
145
151
|
click.echo("------------")
|
|
@@ -1623,7 +1629,7 @@ def run_aws_iamrole_connection_flow(
|
|
|
1623
1629
|
local_unavailable: bool = False,
|
|
1624
1630
|
) -> Tuple[str, str, Optional[TinyB], Optional[TinyB]]:
|
|
1625
1631
|
"""
|
|
1626
|
-
Run the interactive AWS IAM Role connection flow for S3.
|
|
1632
|
+
Run the interactive AWS IAM Role connection flow for S3 or DynamoDB.
|
|
1627
1633
|
|
|
1628
1634
|
Guides the user through creating an IAM policy and role with the appropriate
|
|
1629
1635
|
trust policy that includes AWS account IDs from the selected environments.
|
|
@@ -1631,7 +1637,7 @@ def run_aws_iamrole_connection_flow(
|
|
|
1631
1637
|
Args:
|
|
1632
1638
|
config: The CLI configuration dictionary.
|
|
1633
1639
|
client: The TinyB client instance.
|
|
1634
|
-
service: The data connector service type (e.g., 's3').
|
|
1640
|
+
service: The data connector service type (e.g., 's3' or 'dynamodb').
|
|
1635
1641
|
connection_name: The name for the connection being created.
|
|
1636
1642
|
policy: The access policy type ('read' or 'write').
|
|
1637
1643
|
local_unavailable: If True, local environment is unavailable (e.g., missing AWS credentials).
|
|
@@ -1639,23 +1645,41 @@ def run_aws_iamrole_connection_flow(
|
|
|
1639
1645
|
Returns:
|
|
1640
1646
|
A tuple containing:
|
|
1641
1647
|
- role_arn (str): The AWS IAM Role ARN entered by the user.
|
|
1642
|
-
- region (str): The AWS region where the
|
|
1648
|
+
- region (str): The AWS region where the resource is located.
|
|
1643
1649
|
- cloud_client (Optional[TinyB]): The TinyB client instance for the cloud environment.
|
|
1644
1650
|
- local_client (Optional[TinyB]): The TinyB client instance for the local environment.
|
|
1645
1651
|
"""
|
|
1652
|
+
bucket_name: Optional[str]
|
|
1653
|
+
table_name: Optional[str] = None
|
|
1654
|
+
region_prompt: str
|
|
1646
1655
|
if service == DataConnectorType.AMAZON_DYNAMODB:
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
)
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
+
table_name = click.prompt(
|
|
1657
|
+
FeedbackManager.highlight(
|
|
1658
|
+
message="? DynamoDB table name (specific name recommended, use '*' for unrestricted access in IAM policy)"
|
|
1659
|
+
),
|
|
1660
|
+
prompt_suffix="\n> ",
|
|
1661
|
+
)
|
|
1662
|
+
validate_string_connector_param("DynamoDB table name", table_name)
|
|
1663
|
+
bucket_name = click.prompt(
|
|
1664
|
+
FeedbackManager.highlight(
|
|
1665
|
+
message="? Export bucket name (specific name recommended, use '*' for unrestricted access in IAM policy)"
|
|
1666
|
+
),
|
|
1667
|
+
prompt_suffix="\n> ",
|
|
1668
|
+
)
|
|
1669
|
+
validate_string_connector_param("Export bucket name", bucket_name)
|
|
1670
|
+
region_prompt = "? Region (the region where the DynamoDB table is located)"
|
|
1671
|
+
else:
|
|
1672
|
+
bucket_name = click.prompt(
|
|
1673
|
+
FeedbackManager.highlight(
|
|
1674
|
+
message="? Bucket name (specific name recommended, use '*' for unrestricted access in IAM policy)"
|
|
1675
|
+
),
|
|
1676
|
+
prompt_suffix="\n> ",
|
|
1677
|
+
)
|
|
1678
|
+
validate_string_connector_param("Bucket", bucket_name)
|
|
1679
|
+
region_prompt = "? Region (the region where the bucket is located)"
|
|
1656
1680
|
|
|
1657
1681
|
region = click.prompt(
|
|
1658
|
-
FeedbackManager.highlight(message=
|
|
1682
|
+
FeedbackManager.highlight(message=region_prompt),
|
|
1659
1683
|
default="us-east-1",
|
|
1660
1684
|
show_default=True,
|
|
1661
1685
|
prompt_suffix="\n> ",
|
|
@@ -1738,15 +1762,27 @@ def run_aws_iamrole_connection_flow(
|
|
|
1738
1762
|
# Use cloud_client as the main client if local is unavailable
|
|
1739
1763
|
policy_client = cloud_client if local_unavailable and cloud_client else client
|
|
1740
1764
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1765
|
+
if table_name is not None:
|
|
1766
|
+
access_policy, trust_policy, _ = get_aws_iamrole_policies(
|
|
1767
|
+
policy_client,
|
|
1768
|
+
service=service,
|
|
1769
|
+
policy=policy,
|
|
1770
|
+
bucket=bucket_name,
|
|
1771
|
+
table_name=table_name,
|
|
1772
|
+
external_id_seed=connection_name,
|
|
1773
|
+
cloud_client=cloud_client if not local_unavailable else None,
|
|
1774
|
+
local_client=local_client,
|
|
1775
|
+
)
|
|
1776
|
+
else:
|
|
1777
|
+
access_policy, trust_policy, _ = get_aws_iamrole_policies(
|
|
1778
|
+
policy_client,
|
|
1779
|
+
service=service,
|
|
1780
|
+
policy=policy,
|
|
1781
|
+
bucket=bucket_name,
|
|
1782
|
+
external_id_seed=connection_name,
|
|
1783
|
+
cloud_client=cloud_client if not local_unavailable else None,
|
|
1784
|
+
local_client=local_client,
|
|
1785
|
+
)
|
|
1750
1786
|
|
|
1751
1787
|
click.echo(FeedbackManager.gray(message="\n» Step 1: AWS Authentication"))
|
|
1752
1788
|
click.echo(
|
|
@@ -1756,7 +1792,10 @@ def run_aws_iamrole_connection_flow(
|
|
|
1756
1792
|
)
|
|
1757
1793
|
click.echo(
|
|
1758
1794
|
FeedbackManager.info(
|
|
1759
|
-
message=
|
|
1795
|
+
message=(
|
|
1796
|
+
"You'll be creating a single IAM Policy and Role to access your data. "
|
|
1797
|
+
"Using IAM Roles improves security by providing temporary credentials and following least privilege principles."
|
|
1798
|
+
)
|
|
1760
1799
|
)
|
|
1761
1800
|
)
|
|
1762
1801
|
click.echo(FeedbackManager.click_enter_to_continue())
|
|
@@ -1781,7 +1820,7 @@ def run_aws_iamrole_connection_flow(
|
|
|
1781
1820
|
click.echo(FeedbackManager.info(message="3. Copy and paste the following policy:"))
|
|
1782
1821
|
click.echo(FeedbackManager.highlight(message=f"\n{access_policy}\n"))
|
|
1783
1822
|
click.echo(
|
|
1784
|
-
FeedbackManager.info(message=f"4. Name the policy something meaningful (e.g.,
|
|
1823
|
+
FeedbackManager.info(message=f"4. Name the policy something meaningful (e.g., TinybirdAccess-{bucket_name})")
|
|
1785
1824
|
)
|
|
1786
1825
|
click.echo(FeedbackManager.info(message="5. Click 'Create policy'"))
|
|
1787
1826
|
click.echo(FeedbackManager.click_enter_to_continue())
|
|
@@ -1807,7 +1846,7 @@ def run_aws_iamrole_connection_flow(
|
|
|
1807
1846
|
click.echo(FeedbackManager.highlight(message=f"\n{trust_policy}\n"))
|
|
1808
1847
|
click.echo(FeedbackManager.info(message="4. Click Next, search for and select the policy you just created"))
|
|
1809
1848
|
click.echo(
|
|
1810
|
-
FeedbackManager.info(message=f"5. Name the role something meaningful (e.g.,
|
|
1849
|
+
FeedbackManager.info(message=f"5. Name the role something meaningful (e.g., TinybirdRole-{bucket_name})")
|
|
1811
1850
|
)
|
|
1812
1851
|
click.echo(FeedbackManager.info(message="6. Click 'Create role'"))
|
|
1813
1852
|
click.echo(FeedbackManager.info(message="7. Copy the Role ARN from the role details page"))
|
|
@@ -1954,6 +1993,7 @@ def get_aws_iamrole_policies(
|
|
|
1954
1993
|
service: str,
|
|
1955
1994
|
policy: str = "write",
|
|
1956
1995
|
bucket: Optional[str] = None,
|
|
1996
|
+
table_name: Optional[str] = None,
|
|
1957
1997
|
external_id_seed: Optional[str] = None,
|
|
1958
1998
|
cloud_client: Optional[TinyB] = None,
|
|
1959
1999
|
local_client: Optional[TinyB] = None,
|
|
@@ -1979,7 +2019,7 @@ def get_aws_iamrole_policies(
|
|
|
1979
2019
|
if policy == "write":
|
|
1980
2020
|
access_policy = client.get_access_write_policy(service, bucket)
|
|
1981
2021
|
elif policy == "read":
|
|
1982
|
-
access_policy = client.get_access_read_policy(service, bucket)
|
|
2022
|
+
access_policy = client.get_access_read_policy(service, bucket, table_name=table_name)
|
|
1983
2023
|
else:
|
|
1984
2024
|
raise Exception(f"Access policy {policy} not supported. Choose from 'read' or 'write'")
|
|
1985
2025
|
if not len(access_policy) > 0:
|
|
@@ -20,6 +20,7 @@ from tinybird.tb.modules.common import (
|
|
|
20
20
|
get_gcs_svc_account_creds,
|
|
21
21
|
run_gcp_svc_account_connection_flow,
|
|
22
22
|
)
|
|
23
|
+
from tinybird.tb.modules.connection_dynamodb import connection_create_dynamodb
|
|
23
24
|
from tinybird.tb.modules.connection_kafka import (
|
|
24
25
|
connection_create_kafka,
|
|
25
26
|
echo_kafka_data,
|
|
@@ -248,6 +249,51 @@ def connection_create_gcs(ctx: Context) -> None:
|
|
|
248
249
|
)
|
|
249
250
|
|
|
250
251
|
|
|
252
|
+
@connection_create.command(
|
|
253
|
+
name="dynamodb", short_help="Creates a AWS DynamoDB connection using IAM role authentication."
|
|
254
|
+
)
|
|
255
|
+
@click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
|
|
256
|
+
@click.option("--table-arn", default=None, help="Optional. Validate the connection against this DynamoDB table ARN")
|
|
257
|
+
@click.pass_context
|
|
258
|
+
def connection_create_dynamodb_cmd(ctx: Context, connection_name: Optional[str], table_arn: Optional[str]) -> None:
|
|
259
|
+
"""
|
|
260
|
+
Creates a AWS DynamoDB connection using IAM role authentication.
|
|
261
|
+
|
|
262
|
+
\b
|
|
263
|
+
$ tb connection create dynamodb
|
|
264
|
+
"""
|
|
265
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
266
|
+
project: Project = obj["project"]
|
|
267
|
+
client: TinyB = obj["client"]
|
|
268
|
+
env: str = obj["env"]
|
|
269
|
+
config = obj["config"]
|
|
270
|
+
|
|
271
|
+
local_aws_unavailable = env == "local" and not client.check_aws_credentials()
|
|
272
|
+
|
|
273
|
+
if env == "local" and not local_aws_unavailable:
|
|
274
|
+
click.echo(FeedbackManager.gray(message="» Building project before continue..."))
|
|
275
|
+
error = build_project(project=project, tb_client=client, watch=False, config=config, silent=True)
|
|
276
|
+
if error:
|
|
277
|
+
click.echo(FeedbackManager.error(message=error))
|
|
278
|
+
else:
|
|
279
|
+
click.echo(FeedbackManager.success(message="✓ Build completed"))
|
|
280
|
+
|
|
281
|
+
result = connection_create_dynamodb(ctx, connection_name=connection_name, table_arn=table_arn)
|
|
282
|
+
|
|
283
|
+
if env == "local" and not local_aws_unavailable and not result["error"]:
|
|
284
|
+
click.echo(FeedbackManager.gray(message="» Building project to access the new connection..."))
|
|
285
|
+
error = build_project(project=project, tb_client=client, watch=False, config=config, silent=True)
|
|
286
|
+
if error:
|
|
287
|
+
click.echo(FeedbackManager.error(message=error))
|
|
288
|
+
else:
|
|
289
|
+
click.echo(FeedbackManager.success(message="✓ Build completed"))
|
|
290
|
+
|
|
291
|
+
# connection_create_dynamodb already prints the failure details; exit non-zero so scripted/CI
|
|
292
|
+
# usage does not treat a connection with missing secrets as a success.
|
|
293
|
+
if result["error"]:
|
|
294
|
+
ctx.exit(1)
|
|
295
|
+
|
|
296
|
+
|
|
251
297
|
@connection_create.command(name="kafka", help="Create a Kafka connection.")
|
|
252
298
|
@click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
|
|
253
299
|
@click.option("--bootstrap-servers", default=None, help="Kafka Bootstrap Server in form mykafka.mycloud.com:9092")
|