Skip to content

Commit f3d9d63

Browse files
authored
Merge pull request #19 from tinybirdco/add-dynamodb-connector-to-sdk
Add DynamoDB connector to Python SDK
2 parents 1d0c8de + ac8c74b commit f3d9d63

17 files changed

Lines changed: 1075 additions & 536 deletions

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,13 @@ Use a local Tinybird container for development without affecting cloud workspace
516516
### Connections
517517

518518
```python
519-
from tinybird_sdk import define_gcs_connection, define_kafka_connection, define_s3_connection, secret
519+
from tinybird_sdk import (
520+
define_dynamodb_connection,
521+
define_gcs_connection,
522+
define_kafka_connection,
523+
define_s3_connection,
524+
secret,
525+
)
520526

521527
events_kafka = define_kafka_connection(
522528
"events_kafka",
@@ -543,6 +549,42 @@ landing_gcs = define_gcs_connection(
543549
"service_account_credentials_json": secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON"),
544550
},
545551
)
552+
553+
events_dynamodb = define_dynamodb_connection(
554+
"events_dynamodb",
555+
{
556+
"region": "us-east-1",
557+
"arn": secret("DYNAMODB_ROLE_ARN"),
558+
},
559+
)
560+
```
561+
562+
The DynamoDB connector uses an IAM role to authenticate. Reference the connection from
563+
a datasource to import a DynamoDB table:
564+
565+
```python
566+
from tinybird_sdk import column, define_datasource, engine, t
567+
568+
orders = define_datasource(
569+
"orders",
570+
{
571+
"schema": {
572+
"id": column(t.string(), {"json_path": "$.Item.id"}),
573+
"_record": column(t.string(), {"json_path": "$.NewImage"}),
574+
"_timestamp": column(t.date_time64(3), {"json_path": "$.ApproximateCreationDateTime"}),
575+
"_event_name": column(t.string().low_cardinality(), {"json_path": "$.eventName"}),
576+
"_is_deleted": column(t.uint8(), {"json_path": "$._is_deleted"}),
577+
},
578+
"engine": engine.replacing_merge_tree(
579+
{"sorting_key": ["id"], "ver": "_timestamp", "is_deleted": "_is_deleted"}
580+
),
581+
"dynamodb": {
582+
"connection": events_dynamodb,
583+
"table_arn": "arn:aws:dynamodb:us-east-1:123456789012:table/orders",
584+
"export_bucket": "s3://my-tinybird-dynamodb-exports",
585+
},
586+
},
587+
)
546588
```
547589

548590
### Datasources

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "tinybird-sdk"
3-
version = "0.1.9"
3+
version = "0.2.0"
44
description = "Python SDK for Tinybird Forward"
55
readme = "README.md"
66
license = "MIT"
@@ -9,7 +9,7 @@ authors = [
99
]
1010
requires-python = ">=3.11"
1111
dependencies = [
12-
"tinybird==4.5.0",
12+
"tinybird==4.6.0",
1313
]
1414
classifiers = [
1515
"Development Status :: 4 - Beta",

src/tinybird_sdk/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@
2626
"define_kafka_connection": ("tinybird_sdk.schema", "define_kafka_connection"),
2727
"define_s3_connection": ("tinybird_sdk.schema", "define_s3_connection"),
2828
"define_gcs_connection": ("tinybird_sdk.schema", "define_gcs_connection"),
29+
"define_dynamodb_connection": ("tinybird_sdk.schema", "define_dynamodb_connection"),
2930
"get_connection_type": ("tinybird_sdk.schema", "get_connection_type"),
3031
"is_connection_definition": ("tinybird_sdk.schema", "is_connection_definition"),
3132
"is_kafka_connection_definition": ("tinybird_sdk.schema", "is_kafka_connection_definition"),
3233
"is_s3_connection_definition": ("tinybird_sdk.schema", "is_s3_connection_definition"),
3334
"is_gcs_connection_definition": ("tinybird_sdk.schema", "is_gcs_connection_definition"),
35+
"is_dynamodb_connection_definition": (
36+
"tinybird_sdk.schema",
37+
"is_dynamodb_connection_definition",
38+
),
3439
"secret": ("tinybird_sdk.schema", "secret"),
3540
"define_token": ("tinybird_sdk.schema", "define_token"),
3641
"is_token_definition": ("tinybird_sdk.schema", "is_token_definition"),

src/tinybird_sdk/generator/connection.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ..schema.connection import (
66
ConnectionDefinition,
7+
DynamoDBConnectionDefinition,
78
GCSConnectionDefinition,
89
KafkaConnectionDefinition,
910
S3ConnectionDefinition,
@@ -70,6 +71,17 @@ def _generate_gcs_connection(connection: GCSConnectionDefinition) -> str:
7071
return "\n".join(parts)
7172

7273

74+
def _generate_dynamodb_connection(connection: DynamoDBConnectionDefinition) -> str:
75+
options = connection.options
76+
parts = [
77+
"TYPE dynamodb",
78+
f"DYNAMODB_ARN {options.arn}",
79+
f"DYNAMODB_REGION {options.region}",
80+
]
81+
82+
return "\n".join(parts)
83+
84+
7385
def generate_connection(connection: ConnectionDefinition) -> GeneratedConnection:
7486
if isinstance(connection, KafkaConnectionDefinition):
7587
return GeneratedConnection(
@@ -83,6 +95,10 @@ def generate_connection(connection: ConnectionDefinition) -> GeneratedConnection
8395
return GeneratedConnection(
8496
name=connection._name, content=_generate_gcs_connection(connection)
8597
)
98+
if isinstance(connection, DynamoDBConnectionDefinition):
99+
return GeneratedConnection(
100+
name=connection._name, content=_generate_dynamodb_connection(connection)
101+
)
86102
raise ValueError(f"Unsupported connection type: {connection._connectionType}")
87103

88104

src/tinybird_sdk/generator/datasource.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,15 @@ def _generate_import_config(import_config: Any) -> str:
124124
return "\n".join(lines)
125125

126126

127+
def _generate_dynamodb_config(dynamodb: Any) -> str:
128+
lines = [
129+
f"IMPORT_CONNECTION_NAME {dynamodb.connection._name}",
130+
f"IMPORT_TABLE_ARN {dynamodb.table_arn}",
131+
f"IMPORT_EXPORT_BUCKET {dynamodb.export_bucket}",
132+
]
133+
return "\n".join(lines)
134+
135+
127136
def _generate_forward_query(forward_query: str | None) -> str | None:
128137
if not forward_query or not forward_query.strip():
129138
return None
@@ -193,6 +202,9 @@ def generate_datasource(datasource: DatasourceDefinition) -> GeneratedDatasource
193202
if datasource.options.gcs:
194203
parts.extend(["", _generate_import_config(datasource.options.gcs)])
195204

205+
if datasource.options.dynamodb:
206+
parts.extend(["", _generate_dynamodb_config(datasource.options.dynamodb)])
207+
196208
forward_query = _generate_forward_query(datasource.options.forward_query)
197209
if forward_query:
198210
parts.extend(["", forward_query])

src/tinybird_sdk/migrate/emit_ts.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .parser_utils import parse_literal_from_datafile
99
from .types import (
1010
DatasourceModel,
11+
DynamoDBConnectionModel,
1112
GCSConnectionModel,
1213
KafkaConnectionModel,
1314
ParsedResource,
@@ -261,6 +262,14 @@ def _emit_datasource(ds: DatasourceModel) -> str:
261262
lines.append(f" 'from_timestamp': {_escape_string(ds.gcs.from_timestamp)},")
262263
lines.append(" },")
263264

265+
if ds.dynamodb:
266+
connection_var = to_snake_case(ds.dynamodb.connection_name)
267+
lines.append(" 'dynamodb': {")
268+
lines.append(f" 'connection': {connection_var},")
269+
lines.append(f" 'table_arn': {_escape_string(ds.dynamodb.table_arn)},")
270+
lines.append(f" 'export_bucket': {_escape_string(ds.dynamodb.export_bucket)},")
271+
lines.append(" },")
272+
264273
if ds.forward_query:
265274
lines.append(" 'forward_query': '''")
266275
lines.append(ds.forward_query)
@@ -336,13 +345,31 @@ def _emit_gcs_connection(connection: GCSConnectionModel) -> str:
336345
return "\n".join(lines)
337346

338347

348+
def _emit_dynamodb_connection(connection: DynamoDBConnectionModel) -> str:
349+
variable_name = to_snake_case(connection.name)
350+
lines: list[str] = []
351+
lines.append(
352+
f"{variable_name} = define_dynamodb_connection({_escape_string(connection.name)}, {{"
353+
)
354+
lines.append(f" 'region': {_escape_string(connection.region)},")
355+
lines.append(f" 'arn': {_escape_string(connection.arn)},")
356+
lines.append("})")
357+
lines.append("")
358+
return "\n".join(lines)
359+
360+
339361
def _emit_connection(
340-
connection: KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel,
362+
connection: KafkaConnectionModel
363+
| S3ConnectionModel
364+
| GCSConnectionModel
365+
| DynamoDBConnectionModel,
341366
) -> str:
342367
if isinstance(connection, S3ConnectionModel):
343368
return _emit_s3_connection(connection)
344369
if isinstance(connection, GCSConnectionModel):
345370
return _emit_gcs_connection(connection)
371+
if isinstance(connection, DynamoDBConnectionModel):
372+
return _emit_dynamodb_connection(connection)
346373
return _emit_kafka_connection(connection)
347374

348375

@@ -474,6 +501,8 @@ def emit_migration_file_content(resources: list[ParsedResource]) -> str:
474501
imports.add("define_s3_connection")
475502
elif isinstance(conn, GCSConnectionModel):
476503
imports.add("define_gcs_connection")
504+
elif isinstance(conn, DynamoDBConnectionModel):
505+
imports.add("define_dynamodb_connection")
477506
if needs_column:
478507
imports.add("column")
479508
if needs_params:

src/tinybird_sdk/migrate/parse_connection.py

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111
from .types import (
1212
ConnectionModel,
13+
DynamoDBConnectionModel,
1314
GCSConnectionModel,
1415
KafkaConnectionModel,
1516
ResourceFile,
@@ -30,6 +31,8 @@
3031
"S3_ACCESS_KEY",
3132
"S3_SECRET",
3233
"GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON",
34+
"DYNAMODB_ARN",
35+
"DYNAMODB_REGION",
3336
}
3437

3538

@@ -58,6 +61,9 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
5861
access_secret: str | None = None
5962
service_account_credentials_json: str | None = None
6063

64+
dynamodb_arn: str | None = None
65+
dynamodb_region: str | None = None
66+
6167
i = 0
6268
while i < len(lines):
6369
raw_line = lines[i]
@@ -119,6 +125,10 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
119125
access_secret = parse_quoted_value(value)
120126
elif name == "GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON":
121127
service_account_credentials_json = parse_quoted_value(value)
128+
elif name == "DYNAMODB_ARN":
129+
dynamodb_arn = parse_quoted_value(value)
130+
elif name == "DYNAMODB_REGION":
131+
dynamodb_region = parse_quoted_value(value)
122132
else:
123133
raise MigrationParseError(
124134
resource.file_path,
@@ -135,12 +145,20 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
135145
)
136146

137147
if connection_type == "kafka":
138-
if region or arn or access_key or access_secret or service_account_credentials_json:
148+
if (
149+
region
150+
or arn
151+
or access_key
152+
or access_secret
153+
or service_account_credentials_json
154+
or dynamodb_arn
155+
or dynamodb_region
156+
):
139157
raise MigrationParseError(
140158
resource.file_path,
141159
"connection",
142160
resource.name,
143-
"S3/GCS directives are not valid for kafka connections.",
161+
"S3/GCS/DynamoDB directives are not valid for kafka connections.",
144162
)
145163

146164
if not bootstrap_servers:
@@ -175,12 +193,14 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
175193
or schema_registry_url
176194
or ssl_ca_pem
177195
or service_account_credentials_json
196+
or dynamodb_arn
197+
or dynamodb_region
178198
):
179199
raise MigrationParseError(
180200
resource.file_path,
181201
"connection",
182202
resource.name,
183-
"Kafka/GCS directives are not valid for s3 connections.",
203+
"Kafka/GCS/DynamoDB directives are not valid for s3 connections.",
184204
)
185205

186206
if not region:
@@ -231,12 +251,14 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
231251
or arn
232252
or access_key
233253
or access_secret
254+
or dynamodb_arn
255+
or dynamodb_region
234256
):
235257
raise MigrationParseError(
236258
resource.file_path,
237259
"connection",
238260
resource.name,
239-
"Kafka/S3 directives are not valid for gcs connections.",
261+
"Kafka/S3/DynamoDB directives are not valid for gcs connections.",
240262
)
241263

242264
if not service_account_credentials_json:
@@ -255,6 +277,53 @@ def parse_connection_file(resource: ResourceFile) -> ConnectionModel:
255277
service_account_credentials_json=service_account_credentials_json,
256278
)
257279

280+
if connection_type == "dynamodb":
281+
if (
282+
bootstrap_servers
283+
or security_protocol
284+
or sasl_mechanism
285+
or key
286+
or secret
287+
or schema_registry_url
288+
or ssl_ca_pem
289+
or region
290+
or arn
291+
or access_key
292+
or access_secret
293+
or service_account_credentials_json
294+
):
295+
raise MigrationParseError(
296+
resource.file_path,
297+
"connection",
298+
resource.name,
299+
"Kafka/S3/GCS directives are not valid for dynamodb connections.",
300+
)
301+
302+
if not dynamodb_arn:
303+
raise MigrationParseError(
304+
resource.file_path,
305+
"connection",
306+
resource.name,
307+
"DYNAMODB_ARN is required for dynamodb connections.",
308+
)
309+
310+
if not dynamodb_region:
311+
raise MigrationParseError(
312+
resource.file_path,
313+
"connection",
314+
resource.name,
315+
"DYNAMODB_REGION is required for dynamodb connections.",
316+
)
317+
318+
return DynamoDBConnectionModel(
319+
kind="connection",
320+
name=resource.name,
321+
file_path=resource.file_path,
322+
connection_type="dynamodb",
323+
region=dynamodb_region,
324+
arn=dynamodb_arn,
325+
)
326+
258327
raise MigrationParseError(
259328
resource.file_path,
260329
"connection",

0 commit comments

Comments
 (0)