11# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
22from __future__ import annotations
33
4- import shutil
5- import sys
64import tempfile
75from pathlib import Path
8- from typing import TYPE_CHECKING , cast
6+ from typing import TYPE_CHECKING , Literal , cast
97
108import requests
119import yaml
1715from airbyte ._executors .docker import DockerExecutor
1816from airbyte ._executors .local import PathExecutor
1917from airbyte ._executors .python import VenvExecutor
18+ from airbyte ._util .meta import which
2019from airbyte ._util .telemetry import EventState , log_install_state # Non-public API
21- from airbyte .sources .registry import ConnectorMetadata , get_connector_metadata
20+ from airbyte .sources .registry import ConnectorMetadata , InstallType , get_connector_metadata
2221
2322
2423if TYPE_CHECKING :
@@ -77,33 +76,71 @@ def _try_get_source_manifest(source_name: str, manifest_url: str | None) -> dict
7776 return result_1
7877
7978
80- def get_connector_executor ( # noqa: PLR0912, PLR0913, PLR0915 # Too complex
79+ def _get_local_executor (
80+ name : str ,
81+ local_executable : Path | str | Literal [True ],
82+ version : str | None ,
83+ ) -> Executor :
84+ """Get a local executor for a connector."""
85+ if version :
86+ raise exc .PyAirbyteInputError (
87+ message = "Param 'version' is not supported when 'local_executable' is set."
88+ )
89+
90+ if local_executable is True :
91+ # Use the default executable name for the connector
92+ local_executable = name
93+
94+ if isinstance (local_executable , str ):
95+ if "/" in local_executable or "\\ " in local_executable :
96+ # Assume this is a path
97+ local_executable = Path (local_executable ).absolute ()
98+ else :
99+ which_executable : Path | None = which (local_executable )
100+ if not which_executable :
101+ raise exc .AirbyteConnectorExecutableNotFoundError (
102+ connector_name = name ,
103+ context = {
104+ "executable" : name ,
105+ "working_directory" : Path .cwd ().absolute (),
106+ },
107+ ) from FileNotFoundError (name )
108+ local_executable = Path (which_executable ).absolute ()
109+
110+ # `local_executable` is now a Path object
111+
112+ print (f"Using local `{ name } ` executable: { local_executable !s} " )
113+ return PathExecutor (
114+ name = name ,
115+ path = local_executable ,
116+ )
117+
118+
119+ def get_connector_executor ( # noqa: PLR0912, PLR0913 # Too complex
81120 name : str ,
82121 * ,
83122 version : str | None = None ,
84123 pip_url : str | None = None ,
85124 local_executable : Path | str | None = None ,
86- docker_image : bool | str | None = False ,
125+ docker_image : bool | str | None = None ,
87126 use_host_network : bool = False ,
88- source_manifest : bool | dict | Path | str = False ,
127+ source_manifest : bool | dict | Path | str | None = None ,
89128 install_if_missing : bool = True ,
90129 install_root : Path | None = None ,
91130) -> Executor :
92131 """This factory function creates an executor for a connector.
93132
94133 For documentation of each arg, see the function `airbyte.sources.util.get_source()`.
95134 """
96- if (
97- sum (
98- [
99- bool (local_executable ),
100- bool (docker_image ),
101- bool (pip_url ),
102- bool (source_manifest ),
103- ]
104- )
105- > 1
106- ):
135+ install_method_count = sum (
136+ [
137+ bool (local_executable ),
138+ bool (docker_image ),
139+ bool (pip_url ),
140+ bool (source_manifest ),
141+ ]
142+ )
143+ if install_method_count > 1 :
107144 raise exc .PyAirbyteInputError (
108145 message = (
109146 "You can only specify one of the settings: 'local_executable', 'docker_image', "
@@ -116,40 +153,37 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0915 # Too complex
116153 "source_manifest" : source_manifest ,
117154 },
118155 )
156+ metadata : ConnectorMetadata | None = None
157+ try :
158+ metadata = get_connector_metadata (name )
159+ except exc .AirbyteConnectorNotRegisteredError as ex :
160+ if install_method_count == 0 :
161+ # User has not specified how to install the connector, and it is not registered.
162+ # Fail the install.
163+ log_install_state (name , state = EventState .FAILED , exception = ex )
164+ raise
119165
120- if local_executable :
121- if version :
122- raise exc .PyAirbyteInputError (
123- message = "Param 'version' is not supported when 'local_executable' is set."
124- )
166+ if install_method_count == 0 :
167+ # User has not specified how to install the connector.
168+ # Prefer local executable if found, then manifests, then python, then docker, depending upon
169+ # how the connector is declared in the connector registry.
170+ if which (name ):
171+ local_executable = name
172+ elif metadata and metadata .install_types :
173+ match metadata .default_install_type :
174+ case InstallType .YAML :
175+ source_manifest = True
176+ case InstallType .PYTHON :
177+ pip_url = metadata .pypi_package_name
178+ case _:
179+ docker_image = True
125180
126- if isinstance (local_executable , str ):
127- if "/" in local_executable or "\\ " in local_executable :
128- # Assume this is a path
129- local_executable = Path (local_executable ).absolute ()
130- else :
131- which_executable : str | None = None
132- which_executable = shutil .which (local_executable )
133- if not which_executable and sys .platform == "win32" :
134- # Try with the .exe extension
135- local_executable = f"{ local_executable } .exe"
136- which_executable = shutil .which (local_executable )
137-
138- if which_executable is None :
139- raise exc .AirbyteConnectorExecutableNotFoundError (
140- connector_name = name ,
141- context = {
142- "executable" : local_executable ,
143- "working_directory" : Path .cwd ().absolute (),
144- },
145- ) from FileNotFoundError (local_executable )
146- local_executable = Path (which_executable ).absolute ()
147-
148- print (f"Using local `{ name } ` executable: { local_executable !s} " )
149- return PathExecutor (
150- name = name ,
151- path = local_executable ,
152- )
181+ if local_executable :
182+ return _get_local_executor (
183+ name = name ,
184+ local_executable = local_executable ,
185+ version = version ,
186+ )
153187
154188 if docker_image :
155189 if docker_image is True :
@@ -215,15 +249,6 @@ def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0915 # Too complex
215249
216250 # else: we are installing a connector in a Python virtual environment:
217251
218- metadata : ConnectorMetadata | None = None
219- try :
220- metadata = get_connector_metadata (name )
221- except exc .AirbyteConnectorNotRegisteredError as ex :
222- if not pip_url :
223- log_install_state (name , state = EventState .FAILED , exception = ex )
224- # We don't have a pip url or registry entry, so we can't install the connector
225- raise
226-
227252 try :
228253 executor = VenvExecutor (
229254 name = name ,
0 commit comments