Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f801ff0
feat: Add Python version compatibility checking during connector inst…
devin-ai-integration[bot] Jul 31, 2025
7844473
fix: Add packaging as explicit dependency instead of ignoring Deptry …
devin-ai-integration[bot] Jul 31, 2025
09ea2bb
fix: Update poetry.lock after adding packaging dependency
devin-ai-integration[bot] Jul 31, 2025
5b9f787
feat: Add lru_cache decorator to _get_pypi_python_requirements method
devin-ai-integration[bot] Jul 31, 2025
e2d90d9
fix: Address GitHub comments and resolve B019 lru_cache memory leak
devin-ai-integration[bot] Jul 31, 2025
01016f7
fix: Simplify HTTP response handling and remove unnecessary wrapper m…
devin-ai-integration[bot] Jul 31, 2025
488148b
fix: Use suppress() context manager for cleaner exception handling
devin-ai-integration[bot] Jul 31, 2025
fc78f31
fix: Update _check_python_version_compatibility to return bool | None
devin-ai-integration[bot] Jul 31, 2025
949601c
fix: Remove try/catch/else block from _check_python_version_compatibi…
devin-ai-integration[bot] Jul 31, 2025
68a85ba
Apply suggestion from @aaronsteers
aaronsteers Jul 31, 2025
69bc091
Merge branch 'main' into devin/1753929963-python-version-compatibilit…
aaronsteers Jul 31, 2025
b05a927
fix: Resolve IndentationError and add comprehensive unit tests
devin-ai-integration[bot] Jul 31, 2025
632808c
refactor: Move version compatibility check to util module and update …
devin-ai-integration[bot] Jul 31, 2025
d23f05a
fix: Resolve CI failures from semver refactoring
devin-ai-integration[bot] Jul 31, 2025
76ca14f
Merge branch 'main' into devin/1753929963-python-version-compatibilit…
aaronsteers Aug 1, 2025
14c7e22
Auto-commit Resolving dependencies... changes
Aug 1, 2025
1304916
Merge branch 'main' into devin/1753929963-python-version-compatibilit…
aaronsteers Aug 1, 2025
65d0ca8
Auto-commit Resolving dependencies... changes
Aug 1, 2025
60093f5
debug: Add debug prints to trace version compatibility checking execu…
devin-ai-integration[bot] Aug 1, 2025
2ad4081
fix: Add version compatibility checking to DeclarativeExecutor
devin-ai-integration[bot] Aug 1, 2025
4be7f27
fix: Resolve mypy type error and ensure version checking works for bo…
devin-ai-integration[bot] Aug 1, 2025
23c8a9a
fix: Prevent version compatibility checking during tests to avoid net…
devin-ai-integration[bot] 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
83 changes: 83 additions & 0 deletions airbyte/_executors/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,66 @@
import subprocess
import sys
from contextlib import suppress
from functools import lru_cache
from pathlib import Path
from shutil import rmtree
from typing import TYPE_CHECKING, Literal

import requests
from overrides import overrides
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from rich import print # noqa: A004 # Allow shadowing the built-in

from airbyte import exceptions as exc
from airbyte._executors.base import Executor
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 AIRBYTE_OFFLINE_MODE
from airbyte.logs import warn_once
from airbyte.version import get_version


if TYPE_CHECKING:
from airbyte.sources.registry import ConnectorMetadata


@lru_cache(maxsize=128)
def _get_pypi_python_requirements_cached(package_name: str) -> str | None:
"""Get the requires_python field from PyPI for a package.

Args:
package_name: The PyPI package name to check

Returns:
The requires_python string from PyPI, or None if unavailable

Example:
For airbyte-source-hubspot, returns "<3.12,>=3.10"
"""
if AIRBYTE_OFFLINE_MODE:
return None

url = f"https://pypi.org/pypi/{package_name}/json"
version = get_version()
response = requests.get(
url=url,
headers={"User-Agent": f"PyAirbyte/{version}" if version else "PyAirbyte"},
timeout=10,
)

if not response.ok:
return None

data: dict | None = None
with suppress(Exception):
data = response.json()
if not data:
return None
return data.get("info", {}).get("requires_python")


class VenvExecutor(Executor):
def __init__(
self,
Expand Down Expand Up @@ -106,6 +148,14 @@ def install(self) -> None:

After installation, the installed version will be stored in self.reported_version.
"""
package_name = (
self.metadata.pypi_package_name
if self.metadata and self.metadata.pypi_package_name
else f"airbyte-{self.name}"
)
requires_python = _get_pypi_python_requirements_cached(package_name)
self._check_python_version_compatibility(package_name, requires_python)

self._run_subprocess_and_raise_on_failure(
[sys.executable, "-m", "venv", str(self._get_venv_path())]
)
Expand Down Expand Up @@ -193,6 +243,39 @@ def get_installed_version(

return None

def _check_python_version_compatibility(
self,
package_name: str,
requires_python: str | None,
) -> None:
"""Check if current Python version is compatible with package requirements.

Args:
package_name: Name of the package being checked
requires_python: The requires_python constraint from PyPI (e.g., "<3.12,>=3.10")
"""
if not requires_python:
return

try:
current_version = (
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
)

spec_set = SpecifierSet(requires_python)
current_ver = Version(current_version)

if current_ver not in spec_set:
warn_once(
f"Python version compatibility warning for '{package_name}': "
f"Current Python {current_version} may not be compatible with "
f"package requirement '{requires_python}'. "
f"Installation will proceed but may fail.",
with_stack=False,
)
except Exception:
pass

def ensure_installation(
self,
*,
Expand Down
Loading