Skip to content

Commit a2b051e

Browse files
feat: replace pip with uv and add use_python parameter (#730)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 1a9c411 commit a2b051e

File tree

12 files changed

+550
-93
lines changed

12 files changed

+550
-93
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,47 @@ Watch this [Getting Started Loom video](https://www.loom.com/share/3de81ca3ce914
1818
* [GitHub](https://github.com/airbytehq/quickstarts/blob/main/pyairbyte_notebooks/PyAirbyte_Github_Incremental_Demo.ipynb)
1919
* [Postgres (cache)](https://github.com/airbytehq/quickstarts/blob/main/pyairbyte_notebooks/PyAirbyte_Postgres_Custom_Cache_Demo.ipynb)
2020

21+
## Connector Installation
22+
23+
### Declarative Source Installation
24+
25+
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.
26+
27+
Declarative sources have the fastest download times, due to the simplicity and each of install.
28+
29+
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.
30+
31+
### Python Installation
32+
33+
Generally, when Python-based installation is possible, it will be performed automatically given a Python-based connector name.
34+
35+
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.
36+
37+
#### Installing Connectors with `uv`
38+
39+
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.
40+
41+
If you prefer to fall back to the prior `pip`-based installation methods, set the env var `AIRBYTE_NO_UV=true`.
42+
43+
#### Installing Connectors With a Custom Python Version
44+
45+
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.
46+
47+
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:
48+
49+
```py
50+
import airbyte as ab
51+
52+
source = ab.get_source(
53+
"source-faker",
54+
use_python="3.10.17",
55+
)
56+
```
57+
58+
### Installing Connectors with Docker
59+
60+
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.
61+
2162
## Contributing
2263

2364
To learn how you can contribute to PyAirbyte, please see our [PyAirbyte Contributors Guide](./docs/CONTRIBUTING.md).

airbyte/_executors/python.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from airbyte._util.meta import is_windows
1818
from airbyte._util.telemetry import EventState, log_install_state
1919
from airbyte._util.venv_util import get_bin_dir
20+
from airbyte.constants import NO_UV
2021

2122

2223
if TYPE_CHECKING:
@@ -32,6 +33,7 @@ def __init__(
3233
target_version: str | None = None,
3334
pip_url: str | None = None,
3435
install_root: Path | None = None,
36+
use_python: bool | Path | str | None = None,
3537
) -> None:
3638
"""Initialize a connector executor that runs a connector in a virtual environment.
3739
@@ -42,6 +44,11 @@ def __init__(
4244
pip_url: (Optional.) The pip URL of the connector to install.
4345
install_root: (Optional.) The root directory where the virtual environment will be
4446
created. If not provided, the current working directory will be used.
47+
use_python: (Optional.) Python interpreter specification:
48+
- True: Use current Python interpreter
49+
- False: Use Docker instead (handled by factory)
50+
- Path: Use interpreter at this path or interpreter name/command
51+
- str: Use uv-managed Python version (semver patterns like "3.12", "3.11.5")
4552
"""
4653
super().__init__(name=name, metadata=metadata, target_version=target_version)
4754

@@ -59,6 +66,7 @@ def __init__(
5966
else f"airbyte-{self.name}"
6067
)
6168
self.install_root = install_root or Path.cwd()
69+
self.use_python = use_python
6270

6371
def _get_venv_name(self) -> str:
6472
return f".venv-{self.name}"
@@ -106,20 +114,62 @@ def install(self) -> None:
106114
107115
After installation, the installed version will be stored in self.reported_version.
108116
"""
109-
self._run_subprocess_and_raise_on_failure(
110-
[sys.executable, "-m", "venv", str(self._get_venv_path())]
111-
)
117+
if not (
118+
self.use_python is None
119+
or self.use_python is True
120+
or self.use_python is False
121+
or isinstance(self.use_python, (str, Path))
122+
):
123+
raise exc.PyAirbyteInputError(
124+
message="Invalid use_python parameter type",
125+
input_value=str(self.use_python),
126+
)
127+
128+
python_override: str | None = None
129+
if not NO_UV and isinstance(self.use_python, Path):
130+
python_override = str(self.use_python.absolute())
131+
132+
elif not NO_UV and isinstance(self.use_python, str):
133+
python_override = self.use_python
134+
135+
uv_cmd_prefix = ["uv"] if not NO_UV else []
136+
python_clause: list[str] = ["--python", python_override] if python_override else []
112137

113-
pip_path = str(get_bin_dir(self._get_venv_path()) / "pip")
138+
venv_cmd: list[str] = [
139+
*(uv_cmd_prefix or [sys.executable, "-m"]),
140+
"venv",
141+
str(self._get_venv_path()),
142+
*python_clause,
143+
]
114144
print(
115-
f"Installing '{self.name}' into virtual environment '{self._get_venv_path()!s}'.\n"
116-
f"Running 'pip install {self.pip_url}'...\n",
145+
f"Creating '{self.name}' virtual environment with command '{' '.join(venv_cmd)}'",
146+
file=sys.stderr,
147+
)
148+
self._run_subprocess_and_raise_on_failure(venv_cmd)
149+
150+
install_cmd = (
151+
[
152+
"uv",
153+
"pip",
154+
"install",
155+
"--python", # uv requires --python after the subcommand
156+
str(self.interpreter_path),
157+
]
158+
if not NO_UV
159+
else [
160+
"pip",
161+
"--python", # pip requires --python before the subcommand
162+
str(self.interpreter_path),
163+
"install",
164+
]
165+
) + shlex.split(self.pip_url)
166+
print(
167+
f"Installing '{self.name}' into virtual environment '{self._get_venv_path()!s}' with "
168+
f"command '{' '.join(install_cmd)}'...\n",
117169
file=sys.stderr,
118170
)
119171
try:
120-
self._run_subprocess_and_raise_on_failure(
121-
args=[pip_path, "install", *shlex.split(self.pip_url)]
122-
)
172+
self._run_subprocess_and_raise_on_failure(install_cmd)
123173
except exc.AirbyteSubprocessFailedError as ex:
124174
# If the installation failed, remove the virtual environment
125175
# Otherwise, the connector will be considered as installed and the user may not be able

airbyte/_executors/util.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
166166
source_manifest: bool | dict | Path | str | None = None,
167167
install_if_missing: bool = True,
168168
install_root: Path | None = None,
169+
use_python: bool | Path | str | None = None,
169170
) -> Executor:
170171
"""This factory function creates an executor for a connector.
171172
@@ -175,11 +176,18 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
175176
[
176177
bool(local_executable),
177178
bool(docker_image),
178-
bool(pip_url),
179+
bool(pip_url) or bool(use_python),
179180
bool(source_manifest),
180181
]
181182
)
182183

184+
if use_python is False:
185+
docker_image = True
186+
187+
if use_python is None and pip_url is not None:
188+
# If pip_url is set, we assume the user wants to use Python.
189+
use_python = True
190+
183191
if version and pip_url:
184192
raise exc.PyAirbyteInputError(
185193
message=(
@@ -341,6 +349,7 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0914, PLR0915, C901 #
341349
target_version=version,
342350
pip_url=pip_url,
343351
install_root=install_root,
352+
use_python=use_python,
344353
)
345354
if install_if_missing:
346355
executor.ensure_installation()

0 commit comments

Comments
 (0)