Skip to content

Commit 53e125e

Browse files
committed
I/O: API improvements: ctk {load,save} table becomes ctk {load,save}
Also, the `--cluster-url` option becomes optional. That's a much more concise interface now.
1 parent cd73d77 commit 53e125e

File tree

52 files changed

+370
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+370
-347
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

33
## Unreleased
4+
- I/O: API improvements: `ctk {load,save} table` became `ctk {load,save}`
5+
Also, the `--cluster-url` option becomes optional. That's a much more
6+
concise interface now.
47

58
## 2026/03/13 v0.0.45
69
- I/O: Refactored `ctk.io` subsystem

cratedb_toolkit/cluster/core.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def load_table(
350350
Synopsis
351351
--------
352352
export CRATEDB_CLUSTER_ID=95998958-4d96-46eb-a77a-a894e7dde128
353-
ctk load table https://cdn.crate.io/downloads/datasets/cratedb-datasets/cloud-tutorials/data_weather.csv.gz
353+
ctk load https://cdn.crate.io/downloads/datasets/cratedb-datasets/cloud-tutorials/data_weather.csv.gz
354354
355355
https://console.cratedb.cloud
356356
"""
@@ -360,7 +360,7 @@ def load_table(
360360
target = target or TableAddress()
361361

362362
if self.cluster_id is None:
363-
raise DatabaseAddressMissingError("Need cluster identifier to load table")
363+
raise DatabaseAddressMissingError("Need cluster identifier to load data")
364364

365365
try:
366366
cio = CloudIo(cluster_id=self.cluster_id)
@@ -539,10 +539,10 @@ def load_table(
539539
--------
540540
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
541541
542-
ctk load table influxdb2://example:token@localhost:8086/testdrive/demo
543-
ctk load table mongodb://localhost:27017/testdrive/demo
544-
ctk load table kinesis+dms:///arn:aws:kinesis:eu-central-1:831394476016:stream/testdrive
545-
ctk load table kinesis+dms:///path/to/dms-over-kinesis.jsonl
542+
ctk load influxdb2://example:token@localhost:8086/testdrive/demo
543+
ctk load mongodb://localhost:27017/testdrive/demo
544+
ctk load kinesis+dms:///arn:aws:kinesis:eu-central-1:831394476016:stream/testdrive
545+
ctk load kinesis+dms:///path/to/dms-over-kinesis.jsonl
546546
"""
547547

548548
self._load_table_result = self._router.load_table(
@@ -563,7 +563,7 @@ def save_table(
563563
--------
564564
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
565565
566-
ctk save table \
566+
ctk save \
567567
"file+iceberg://./var/lib/iceberg/?catalog=default&namespace=demo&table=taxi_dataset"
568568
"""
569569

cratedb_toolkit/io/cli.py

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,20 @@
33
from pathlib import Path
44

55
import click
6-
from click_aliases import ClickAliasedGroup
76

87
from cratedb_toolkit.cluster.core import DatabaseCluster
98
from cratedb_toolkit.model import InputOutputResource, TableAddress
109
from cratedb_toolkit.option import option_cluster_id, option_cluster_name, option_cluster_url
11-
from cratedb_toolkit.util.cli import boot_click, make_command
10+
from cratedb_toolkit.util.cli import boot_click
1211

1312
logger = logging.getLogger(__name__)
1413

1514

16-
@click.group(cls=ClickAliasedGroup) # type: ignore[arg-type]
15+
@click.command()
16+
@click.argument("source_url")
17+
@click.argument("target_url", required=False)
1718
@click.option("--verbose", is_flag=True, required=False, help="Turn on logging")
1819
@click.option("--debug", is_flag=True, required=False, help="Turn on logging with debug level")
19-
@click.version_option()
20-
@click.pass_context
21-
def cli_load(ctx: click.Context, verbose: bool, debug: bool):
22-
"""
23-
Load data into CrateDB.
24-
"""
25-
return boot_click(ctx, verbose, debug)
26-
27-
28-
@make_command(cli_load, name="table")
29-
@click.argument("url")
3020
@option_cluster_id
3121
@option_cluster_name
3222
@option_cluster_url
@@ -36,9 +26,10 @@ def cli_load(ctx: click.Context, verbose: bool, debug: bool):
3626
@click.option("--compression", type=str, required=False, help="Compression format of the import resource")
3727
@click.option("--transformation", type=Path, required=False, help="Path to Tikray transformation file")
3828
@click.pass_context
39-
def load_table(
29+
def cli_load(
4030
ctx: click.Context,
41-
url: str,
31+
source_url: str,
32+
target_url: str,
4233
cluster_id: str,
4334
cluster_name: str,
4435
cluster_url: str,
@@ -47,20 +38,30 @@ def load_table(
4738
format_: str,
4839
compression: str,
4940
transformation: t.Union[Path, None],
41+
verbose: bool,
42+
debug: bool,
5043
):
5144
"""
52-
Import data into CrateDB and CrateDB Cloud clusters.
45+
Load data into CrateDB.
5346
"""
5447

48+
boot_click(ctx, verbose, debug)
49+
50+
# API evolution adjustments.
51+
if target_url and cluster_url and target_url != cluster_url:
52+
raise click.UsageError("Specify cluster URL either as TARGET_URL or --cluster-url, not both.")
53+
if target_url:
54+
cluster_url = target_url
55+
5556
# When `--transformation` is given, but empty, fix it.
5657
if transformation is not None and transformation.name == "":
5758
transformation = None
5859

5960
# Encapsulate source and target parameters.
60-
source = InputOutputResource(url=url, format=format_, compression=compression)
61+
source = InputOutputResource(url=source_url, format=format_, compression=compression)
6162
target = TableAddress(schema=schema, table=table)
6263

63-
# Dispatch "load table" operation.
64+
# Dispatch "load" operation.
6465
cluster = DatabaseCluster.create(
6566
cluster_id=cluster_id,
6667
cluster_name=cluster_name,
@@ -69,49 +70,58 @@ def load_table(
6970
cluster.load_table(source=source, target=target, transformation=transformation)
7071

7172

72-
@click.group(cls=ClickAliasedGroup) # type: ignore[arg-type]
73+
@click.command()
74+
@click.argument("source_url", required=False)
75+
@click.argument("target_url", required=False)
7376
@click.option("--verbose", is_flag=True, required=False, help="Turn on logging")
7477
@click.option("--debug", is_flag=True, required=False, help="Turn on logging with debug level")
75-
@click.version_option()
76-
@click.pass_context
77-
def cli_save(ctx: click.Context, verbose: bool, debug: bool):
78-
"""
79-
Export data from CrateDB.
80-
"""
81-
return boot_click(ctx, verbose, debug)
82-
83-
84-
@make_command(cli_save, name="table")
85-
@click.argument("url")
8678
@option_cluster_id
8779
@option_cluster_name
8880
@option_cluster_url
8981
@click.option("--format", "format_", type=str, required=False, help="File format of the export resource")
9082
@click.option("--compression", type=str, required=False, help="Compression format of the export resource")
9183
@click.option("--transformation", type=Path, required=False, help="Path to Tikray transformation file")
9284
@click.pass_context
93-
def save_table(
85+
def cli_save(
9486
ctx: click.Context,
95-
url: str,
87+
source_url: t.Optional[str],
88+
target_url: t.Optional[str],
9689
cluster_id: str,
9790
cluster_name: str,
9891
cluster_url: str,
9992
format_: str,
10093
compression: str,
10194
transformation: t.Union[Path, None],
95+
verbose: bool,
96+
debug: bool,
10297
):
10398
"""
104-
Export data from CrateDB and CrateDB Cloud clusters.
99+
Export data from CrateDB.
105100
"""
106101

102+
boot_click(ctx, verbose, debug)
103+
104+
# API evolution adjustments.
105+
if source_url and not target_url:
106+
target_url = source_url
107+
source_url = None
108+
if source_url and cluster_url and source_url != cluster_url:
109+
raise click.UsageError("Specify cluster URL either as SOURCE_URL or --cluster-url, not both.")
110+
if source_url:
111+
cluster_url = source_url
112+
if not target_url:
113+
raise click.UsageError(
114+
"Missing TARGET_URL. Use `ctk save <target-url>` or `ctk save <cluster-url> <target-url>`."
115+
)
116+
107117
# When `--transformation` is given, but empty, fix it.
108118
if transformation is not None and transformation.name == "":
109119
transformation = None
110120

111121
# Encapsulate source and target parameters.
112-
target = InputOutputResource(url=url, format=format_, compression=compression)
122+
target = InputOutputResource(url=target_url, format=format_, compression=compression)
113123

114-
# Dispatch "save table" operation.
124+
# Dispatch "save" operation.
115125
cluster = DatabaseCluster.create(
116126
cluster_id=cluster_id,
117127
cluster_name=cluster_name,

cratedb_toolkit/io/deltalake/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ def from_deltalake(source_url, target_url, progress: bool = False):
102102
See also: https://docs.pola.rs/api/python/stable/reference/api/polars.scan_delta.html
103103
104104
# Synopsis: Load from filesystem.
105-
ctk load table \
105+
ctk load \
106106
"file+deltalake://./var/lib/delta" \
107-
--cluster-url="crate://crate@localhost:4200/demo/taxi"
107+
"crate://crate@localhost:4200/demo/taxi"
108108
"""
109109
source = DeltaLakeAddress.from_url(source_url)
110110
logger.info(f"DeltaLake address: {source.location}")
@@ -123,8 +123,8 @@ def to_deltalake(source_url, target_url, progress: bool = False):
123123
See also: https://docs.pola.rs/api/python/stable/reference/api/polars.DataFrame.write_delta.html
124124
125125
# Synopsis: Save to filesystem.
126-
ctk save table \
127-
--cluster-url="crate://crate@localhost:4200/demo/taxi" \
126+
ctk save \
127+
"crate://crate@localhost:4200/demo/taxi" \
128128
"file+deltalake://./var/lib/delta"
129129
"""
130130

cratedb_toolkit/io/dynamodb/api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ def dynamodb_copy(source_url, target_url, progress: bool = False):
1111
Synopsis
1212
--------
1313
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
14-
ctk load table dynamodb://AWS_ACCESS_KEY:AWS_SECRET_ACCESS_KEY@localhost:4566/us-east-1/ProductCatalog
15-
ctk load table dynamodb://AWS_ACCESS_KEY:AWS_SECRET_ACCESS_KEY@localhost:4566/arn:aws:dynamodb:us-east-1:000000000000:table/ProductCatalog
14+
ctk load dynamodb://AWS_ACCESS_KEY:AWS_SECRET_ACCESS_KEY@localhost:4566/us-east-1/ProductCatalog
15+
ctk load dynamodb://AWS_ACCESS_KEY:AWS_SECRET_ACCESS_KEY@localhost:4566/arn:aws:dynamodb:us-east-1:000000000000:table/ProductCatalog
1616
17-
ctk load table dynamodb://arn:aws:dynamodb:us-east-1:000000000000:table/ProductCatalog
18-
arn:aws:dynamodb:us-east-1:841394475918:table/stream-demo
17+
ctk load dynamodb://arn:aws:dynamodb:us-east-1:000000000000:table/ProductCatalog
18+
ctk load dynamodb://arn:aws:dynamodb:us-east-1:841394475918:table/stream-demo
1919
20-
ctk load table dynamodb://LSIAQAAAAAAVNCBMPNSG:dummy@localhost:4566/ProductCatalog?region=eu-central-1
20+
ctk load dynamodb://LSIAQAAAAAAVNCBMPNSG:dummy@localhost:4566/ProductCatalog?region=eu-central-1
2121
2222
Resources
2323
---------

cratedb_toolkit/io/iceberg/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ def from_iceberg(source_url, target_url, progress: bool = False):
156156
See also: https://docs.pola.rs/api/python/stable/reference/api/polars.scan_iceberg.html
157157
158158
# Synopsis: Load from metadata file on filesystem.
159-
ctk load table \
159+
ctk load \
160160
"file+iceberg://./iceberg/demo/taxi/metadata/00001-79d5b044-8bce-46dd-b21c-83679a01c986.metadata.json" \
161-
--cluster-url="crate://crate@localhost:4200/demo/taxi"
161+
"crate://crate@localhost:4200/demo/taxi"
162162
"""
163163
source = IcebergAddress.from_url(source_url)
164164
logger.info(f"Iceberg address: {source.location}")
@@ -177,8 +177,8 @@ def to_iceberg(source_url, target_url, progress: bool = False):
177177
See also: https://pandas.pydata.org/docs/dev/reference/api/pandas.DataFrame.to_iceberg.html
178178
179179
# Synopsis: Save to filesystem.
180-
ctk save table \
181-
--cluster-url="crate://crate@localhost:4200/demo/taxi" \
180+
ctk save \
181+
"crate://crate@localhost:4200/demo/taxi" \
182182
"file+iceberg://./iceberg/?catalog=default&namespace=demo&table=taxi"
183183
"""
184184

cratedb_toolkit/io/influxdb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def influxdb_copy(source_url, target_url, progress: bool = False):
1010
Synopsis
1111
--------
1212
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
13-
ctk load table influxdb2://example:token@localhost:8086/testdrive/demo
13+
ctk load influxdb2://example:token@localhost:8086/testdrive/demo
1414
"""
1515

1616
# Invoke copy operation.

cratedb_toolkit/io/ingestr/api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ def ingestr_copy(source_url: str, target_address: DatabaseAddress, progress: boo
5151
5252
Synopsis:
5353
54-
ctk load table \
54+
ctk load \
5555
"frankfurter://?base=EUR&table=latest" \
56-
--cluster-url="crate://crate:na@localhost:4200/testdrive/exchange_latest"
56+
"crate://crate:na@localhost:4200/testdrive/exchange_latest"
5757
58-
ctk load table \
58+
ctk load \
5959
"frankfurter://?base=EUR&table=currencies" \
60-
--cluster-url="crate://crate:na@localhost:4200/testdrive/exchange_currencies"
60+
"crate://crate:na@localhost:4200/testdrive/exchange_currencies"
6161
62-
ctk load table \
62+
ctk load \
6363
"postgresql://pguser:secret11@postgresql.example.org:5432/postgres?table=public.diamonds" \
64-
--cluster-url="crate://crate:na@localhost:4200/testdrive/ibis_diamonds"
64+
"crate://crate:na@localhost:4200/testdrive/ibis_diamonds"
6565
"""
6666
ingestr_available, ingestr, ConfigFieldMissingException = _get_ingestr()
6767

cratedb_toolkit/io/mongodb/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def mongodb_copy_migr8(
3232
Synopsis
3333
--------
3434
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
35-
ctk load table mongodb://localhost:27017/testdrive/demo
35+
ctk load mongodb://localhost:27017/testdrive/demo
3636
3737
Backlog
3838
-------
@@ -115,7 +115,7 @@ def mongodb_copy(
115115
Synopsis
116116
--------
117117
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
118-
ctk load table mongodb://localhost:27017/testdrive/demo
118+
ctk load mongodb://localhost:27017/testdrive/demo
119119
"""
120120

121121
logger.info(f"mongodb_copy. source={source_url}, target={target_url}")
@@ -196,7 +196,7 @@ def mongodb_relay_cdc(
196196
Synopsis
197197
--------
198198
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo-cdc
199-
ctk load table mongodb+cdc://localhost:27017/testdrive/demo
199+
ctk load mongodb+cdc://localhost:27017/testdrive/demo
200200
201201
Backlog
202202
-------

cratedb_toolkit/io/router.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ def load_table(
3838
--------
3939
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
4040
41-
ctk load table influxdb2://example:token@localhost:8086/testdrive/demo
42-
ctk load table mongodb://localhost:27017/testdrive/demo
43-
ctk load table kinesis+dms:///arn:aws:kinesis:eu-central-1:831394476016:stream/testdrive
44-
ctk load table kinesis+dms:///path/to/dms-over-kinesis.jsonl
41+
ctk load influxdb2://example:token@localhost:8086/testdrive/demo
42+
ctk load mongodb://localhost:27017/testdrive/demo
43+
ctk load kinesis+dms:///arn:aws:kinesis:eu-central-1:831394476016:stream/testdrive
44+
ctk load kinesis+dms:///path/to/dms-over-kinesis.jsonl
4545
"""
4646
source_url = source.url
4747
target_url = target.dburi
@@ -140,7 +140,7 @@ def save_table(
140140
--------
141141
export CRATEDB_CLUSTER_URL=crate://crate@localhost:4200/testdrive/demo
142142
143-
ctk save table \
143+
ctk save \
144144
"file+iceberg://./var/lib/iceberg/?catalog=default&namespace=demo&table=taxi_dataset"
145145
"""
146146
source_url = source.dburi

0 commit comments

Comments
 (0)