Skip to content

Commit c644066

Browse files
authored
Feat: Add sync CLI (#417)
1 parent b355ea9 commit c644066

File tree

1 file changed

+95
-2
lines changed

1 file changed

+95
-2
lines changed

airbyte/cli.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565

6666
from __future__ import annotations
6767

68-
import json
6968
from pathlib import Path
7069
from typing import TYPE_CHECKING, Any
7170

@@ -124,6 +123,12 @@
124123
"""For example, --config='{password: "SECRET:MY_PASSWORD"}'."""
125124
)
126125

126+
PIP_URL_HELP = (
127+
"This can be anything pip accepts, including: a PyPI package name, a local path, "
128+
"a git repository, a git branch ref, etc. Use '.' to install from the current local "
129+
"directory."
130+
)
131+
127132

128133
def _resolve_config(
129134
config: str,
@@ -150,7 +155,7 @@ def _inject_secrets(config_dict: dict[str, Any]) -> None:
150155
message="Config file not found.",
151156
input_value=str(config_path),
152157
)
153-
config_dict = json.loads(config_path.read_text(encoding="utf-8"))
158+
config_dict = yaml.safe_load(config_path.read_text(encoding="utf-8"))
154159

155160
_inject_secrets(config_dict)
156161
return config_dict
@@ -447,6 +452,93 @@ def benchmark(
447452
)
448453

449454

455+
@click.command()
456+
@click.option(
457+
"--source",
458+
type=str,
459+
help=(
460+
"The source name, with an optional version declaration. "
461+
"If the name contains a colon (':'), it will be interpreted as a docker image and tag. "
462+
),
463+
)
464+
@click.option(
465+
"--destination",
466+
type=str,
467+
help=(
468+
"The destination name, with an optional version declaration. "
469+
"If a path is provided, it will be interpreted as a path to the local executable. "
470+
),
471+
)
472+
@click.option(
473+
"--streams",
474+
type=str,
475+
help=(
476+
"A comma-separated list of stream names to select for reading. If set to '*', all streams "
477+
"will be selected. Defaults to '*'."
478+
),
479+
)
480+
@click.option(
481+
"--Sconfig",
482+
"source_config",
483+
type=str,
484+
help="The source config. " + CONFIG_HELP,
485+
)
486+
@click.option(
487+
"--Dconfig",
488+
"destination_config",
489+
type=str,
490+
help="The destination config. " + CONFIG_HELP,
491+
)
492+
@click.option(
493+
"--Spip-url",
494+
"source_pip_url",
495+
type=str,
496+
help="Optional pip URL for the source (Python connectors only). " + PIP_URL_HELP,
497+
)
498+
@click.option(
499+
"--Dpip-url",
500+
"destination_pip_url",
501+
type=str,
502+
help="Optional pip URL for the destination (Python connectors only). " + PIP_URL_HELP,
503+
)
504+
def sync(
505+
*,
506+
source: str,
507+
source_config: str | None = None,
508+
source_pip_url: str | None = None,
509+
destination: str,
510+
destination_config: str | None = None,
511+
destination_pip_url: str | None = None,
512+
streams: str | None = None,
513+
) -> None:
514+
"""Run a sync operation.
515+
516+
Currently, this only supports full refresh syncs. Incremental syncs are not yet supported.
517+
Custom catalog syncs are not yet supported.
518+
"""
519+
destination_obj: Destination
520+
source_obj: Source
521+
522+
source_obj = _resolve_source_job(
523+
source=source,
524+
config=source_config,
525+
streams=streams,
526+
pip_url=source_pip_url,
527+
)
528+
destination_obj = _resolve_destination_job(
529+
destination=destination,
530+
config=destination_config,
531+
pip_url=destination_pip_url,
532+
)
533+
534+
click.echo("Running sync...")
535+
destination_obj.write(
536+
source_data=source_obj,
537+
cache=False,
538+
state_cache=False,
539+
)
540+
541+
450542
@click.group()
451543
def cli() -> None:
452544
"""PyAirbyte CLI."""
@@ -455,6 +547,7 @@ def cli() -> None:
455547

456548
cli.add_command(validate)
457549
cli.add_command(benchmark)
550+
cli.add_command(sync)
458551

459552
if __name__ == "__main__":
460553
cli()

0 commit comments

Comments
 (0)