Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c08fb1
feat: replace pip with uv and add use_python parameter
devin-ai-integration[bot] Jul 31, 2025
649043e
fix: add AIRBYTE_NO_UV environment variable for pip fallback
devin-ai-integration[bot] Jul 31, 2025
ab07359
fix: update test assertion to handle both pip and uv error messages
devin-ai-integration[bot] Jul 31, 2025
30d41ef
chore: remove flaky lowcode connector validation tests
devin-ai-integration[bot] Jul 31, 2025
0f1a93a
chore: update poetry.lock after removing test dependencies
devin-ai-integration[bot] Jul 31, 2025
99caa5a
Delete tests/integration_tests/test_lowcode_connectors.py
aaronsteers Jul 31, 2025
ebc2de0
fix: add uv to deptry ignore list for DEP002
devin-ai-integration[bot] Jul 31, 2025
d911e8c
Merge remote-tracking branch 'origin/devin/1753934781-remove-flaky-lo…
devin-ai-integration[bot] Jul 31, 2025
9756905
fix: make validate.py respect AIRBYTE_NO_UV environment variable
devin-ai-integration[bot] Jul 31, 2025
58261ee
Merge branch 'main' into devin/1753929814-replace-pip-with-uv
aaronsteers Jul 31, 2025
d871a8b
refactor: centralize AIRBYTE_NO_UV logic into AIRBYTE_USE_UV constant
devin-ai-integration[bot] Jul 31, 2025
ac12a41
Apply suggestion from @aaronsteers
aaronsteers Jul 31, 2025
cab88c0
fix: properly escape backslash in Windows path detection
devin-ai-integration[bot] Jul 31, 2025
d14b845
feat: implement semver-based distinction for use_python parameter
devin-ai-integration[bot] Jul 31, 2025
47c77c9
cherry-pick-me: fix docker refs for destinations in cli module
aaronsteers Aug 1, 2025
6f7f53a
cherry-pick-me: fix noop destination config
aaronsteers Aug 1, 2025
7ea9d58
misc fixes
aaronsteers Aug 1, 2025
09853e1
update import
aaronsteers Aug 1, 2025
199985e
Merge branch 'main' into devin/1753929814-replace-pip-with-uv
aaronsteers Aug 1, 2025
d587b30
Merge remote-tracking branch 'origin/devin/1753929814-replace-pip-wit…
devin-ai-integration[bot] Aug 1, 2025
dcd682b
fix: parametrize source_test_installation fixture for uv/pip testing
devin-ai-integration[bot] Aug 1, 2025
2820953
fix: resolve ruff lint errors in python.py and remove unused imports …
devin-ai-integration[bot] Aug 1, 2025
fb3b01b
misc cleanup
aaronsteers Aug 1, 2025
0ae6254
clean up pip_url related to use_python
aaronsteers Aug 1, 2025
8cd7b93
Auto-fix lint and format issues
Aug 1, 2025
21e8e13
fix: format airbyte/_executors/util.py with ruff
devin-ai-integration[bot] Aug 1, 2025
e9ef671
Merge branch 'devin/1753929814-replace-pip-with-uv' of https://git-ma…
devin-ai-integration[bot] Aug 1, 2025
f56e364
fix: parametrize source_test_installation fixture for uv/pip testing
devin-ai-integration[bot] Aug 1, 2025
a3f956f
add docs
aaronsteers Aug 1, 2025
b86e753
Merge branch 'devin/1753929814-replace-pip-with-uv' of https://github…
aaronsteers Aug 1, 2025
f5ad7f0
Update README.md
aaronsteers Aug 1, 2025
66e7ae6
Merge branch 'main' into devin/1753929814-replace-pip-with-uv
aaronsteers Aug 1, 2025
0686e7f
Auto-commit Resolving dependencies... changes
Aug 1, 2025
4266c74
Delete test_cross_version_sync.py
aaronsteers Aug 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,47 @@ Watch this [Getting Started Loom video](https://www.loom.com/share/3de81ca3ce914
* [GitHub](https://github.com/airbytehq/quickstarts/blob/main/pyairbyte_notebooks/PyAirbyte_Github_Incremental_Demo.ipynb)
* [Postgres (cache)](https://github.com/airbytehq/quickstarts/blob/main/pyairbyte_notebooks/PyAirbyte_Postgres_Custom_Cache_Demo.ipynb)

## Connector Installation

### Declarative Source Installation

For Declarative Sources defined in YAML, the installation process will is to simply download the yaml file from the `connectors.airbyte.com` public URLs, and to run them directly as YAML.

Declarative sources have the fastest download times, due to the simplicity and each of install.

In some cases, you may get better stability by using `docker_image=True` in `get_source()`/`get_destination()`, due to the fact that all dependencies are locked within the docker image.

### Python Installation

Generally, when Python-based installation is possible, it will be performed automatically given a Python-based connector name.

In some cases, you may get better stability by using `docker_image=True` in `get_source()`/`get_destination()`, due to the fact that all dependencies are locked within the docker image.

#### Installing Connectors with `uv`

By default, beginning with version `0.29.0`, PyAirbyte defaults to [`uv`](https://docs.astral.sh/uv) instead of `pip` for Python connector installation. Compared with `pip`, `uv` is much faster. It also provides the unique ability of specifying different versions of Python than PyAirbyte is using, and even Python versions which are not already pre-installed on the local workstation.

If you prefer to fall back to the prior `pip`-based installation methods, set the env var `AIRBYTE_NO_UV=true`.

#### Installing Connectors With a Custom Python Version

In both `get_source()` and `get_destination()`, you can provide a `use_python` input arg that is equal to the desired version of Python that you with to use for the given connector. This can be helpful if an older connector doesn't support the version of Python that you are using for PyAirbyte itself.

For example, assuming PyAirbyte is running on Python 3.12, you can install a connector using Python 3.10.13 with the following code snippet:

```py
import airbyte as ab

source = ab.get_source(
"source-faker",
use_python="3.10.17",
)
```

### Installing Connectors with Docker

For any connector (`get_source()`/`get_destination()`), you can specify the `docker_image` argument to `True` to prefer Docker over other default installation methods or `docker_image=MY_IMAGE` to leverage a specific docker image tag for the execution.

## Contributing

To learn how you can contribute to PyAirbyte, please see our [PyAirbyte Contributors Guide](./docs/CONTRIBUTING.md).
Expand Down
68 changes: 59 additions & 9 deletions airbyte/_executors/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from airbyte._util.meta import is_windows
from airbyte._util.telemetry import EventState, log_install_state
from airbyte._util.venv_util import get_bin_dir
from airbyte.constants import NO_UV


if TYPE_CHECKING:
Expand All @@ -32,6 +33,7 @@ def __init__(
target_version: str | None = None,
pip_url: str | None = None,
install_root: Path | None = None,
use_python: bool | Path | str | None = None,
) -> None:
"""Initialize a connector executor that runs a connector in a virtual environment.

Expand All @@ -42,6 +44,11 @@ def __init__(
pip_url: (Optional.) The pip URL of the connector to install.
install_root: (Optional.) The root directory where the virtual environment will be
created. If not provided, the current working directory will be used.
use_python: (Optional.) Python interpreter specification:
- True: Use current Python interpreter
- False: Use Docker instead (handled by factory)
- Path: Use interpreter at this path or interpreter name/command
- str: Use uv-managed Python version (semver patterns like "3.12", "3.11.5")
"""
super().__init__(name=name, metadata=metadata, target_version=target_version)

Expand All @@ -59,6 +66,7 @@ def __init__(
else f"airbyte-{self.name}"
)
self.install_root = install_root or Path.cwd()
self.use_python = use_python

def _get_venv_name(self) -> str:
return f".venv-{self.name}"
Expand Down Expand Up @@ -106,20 +114,62 @@ def install(self) -> None:

After installation, the installed version will be stored in self.reported_version.
"""
self._run_subprocess_and_raise_on_failure(
[sys.executable, "-m", "venv", str(self._get_venv_path())]
)
if not (
self.use_python is None
or self.use_python is True
or self.use_python is False
or isinstance(self.use_python, (str, Path))
):
raise exc.PyAirbyteInputError(
message="Invalid use_python parameter type",
input_value=str(self.use_python),
)

python_override: str | None = None
if not NO_UV and isinstance(self.use_python, Path):
python_override = str(self.use_python.absolute())

elif not NO_UV and isinstance(self.use_python, str):
python_override = self.use_python

uv_cmd_prefix = ["uv"] if not NO_UV else []
python_clause: list[str] = ["--python", python_override] if python_override else []

pip_path = str(get_bin_dir(self._get_venv_path()) / "pip")
venv_cmd: list[str] = [
*(uv_cmd_prefix or [sys.executable, "-m"]),
"venv",
str(self._get_venv_path()),
*python_clause,
]
print(
f"Installing '{self.name}' into virtual environment '{self._get_venv_path()!s}'.\n"
f"Running 'pip install {self.pip_url}'...\n",
f"Creating '{self.name}' virtual environment with command '{' '.join(venv_cmd)}'",
file=sys.stderr,
)
self._run_subprocess_and_raise_on_failure(venv_cmd)

install_cmd = (
[
"uv",
"pip",
"install",
"--python", # uv requires --python after the subcommand
str(self.interpreter_path),
]
if not NO_UV
else [
"pip",
"--python", # pip requires --python before the subcommand
str(self.interpreter_path),
"install",
]
) + shlex.split(self.pip_url)
print(
f"Installing '{self.name}' into virtual environment '{self._get_venv_path()!s}' with "
f"command '{' '.join(install_cmd)}'...\n",
file=sys.stderr,
)
try:
self._run_subprocess_and_raise_on_failure(
args=[pip_path, "install", *shlex.split(self.pip_url)]
)
self._run_subprocess_and_raise_on_failure(install_cmd)
except exc.AirbyteSubprocessFailedError as ex:
# If the installation failed, remove the virtual environment
# Otherwise, the connector will be considered as installed and the user may not be able
Expand Down
11 changes: 10 additions & 1 deletion airbyte/_executors/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
source_manifest: bool | dict | Path | str | None = None,
install_if_missing: bool = True,
install_root: Path | None = None,
use_python: bool | Path | str | None = None,
) -> Executor:
"""This factory function creates an executor for a connector.

Expand All @@ -175,11 +176,18 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
[
bool(local_executable),
bool(docker_image),
bool(pip_url),
bool(pip_url) or bool(use_python),
bool(source_manifest),
]
)

if use_python is False:
docker_image = True

if use_python is None and pip_url is not None:
# If pip_url is set, we assume the user wants to use Python.
use_python = True

if version and pip_url:
raise exc.PyAirbyteInputError(
message=(
Expand Down Expand Up @@ -341,6 +349,7 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
target_version=version,
pip_url=pip_url,
install_root=install_root,
use_python=use_python,
)
if install_if_missing:
executor.ensure_installation()
Expand Down
Loading