fal 0.9.2__py3-none-any.whl → 0.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fal might be problematic. Click here for more details.

Files changed (93) hide show
  1. _fal_testing/utils.py +2 -2
  2. dbt/adapters/fal/__init__.py +21 -0
  3. dbt/adapters/fal/__version__.py +1 -0
  4. dbt/adapters/fal/connections.py +18 -0
  5. dbt/adapters/fal/impl.py +93 -0
  6. dbt/adapters/fal/load_db_profile.py +80 -0
  7. dbt/adapters/fal/wrappers.py +113 -0
  8. dbt/adapters/fal_experimental/__init__.py +11 -0
  9. dbt/adapters/fal_experimental/__version__.py +1 -0
  10. dbt/adapters/fal_experimental/adapter.py +149 -0
  11. dbt/adapters/fal_experimental/adapter_support.py +234 -0
  12. dbt/adapters/fal_experimental/connections.py +72 -0
  13. dbt/adapters/fal_experimental/impl.py +240 -0
  14. dbt/adapters/fal_experimental/support/athena.py +92 -0
  15. dbt/adapters/fal_experimental/support/bigquery.py +74 -0
  16. dbt/adapters/fal_experimental/support/duckdb.py +28 -0
  17. dbt/adapters/fal_experimental/support/postgres.py +88 -0
  18. dbt/adapters/fal_experimental/support/redshift.py +56 -0
  19. dbt/adapters/fal_experimental/support/snowflake.py +76 -0
  20. dbt/adapters/fal_experimental/support/trino.py +26 -0
  21. dbt/adapters/fal_experimental/telemetry/__init__.py +1 -0
  22. dbt/adapters/fal_experimental/telemetry/telemetry.py +411 -0
  23. dbt/adapters/fal_experimental/teleport.py +192 -0
  24. dbt/adapters/fal_experimental/teleport_adapter_support.py +23 -0
  25. dbt/adapters/fal_experimental/teleport_support/duckdb.py +122 -0
  26. dbt/adapters/fal_experimental/teleport_support/snowflake.py +72 -0
  27. dbt/adapters/fal_experimental/utils/__init__.py +50 -0
  28. dbt/adapters/fal_experimental/utils/environments.py +302 -0
  29. dbt/fal/adapters/python/__init__.py +3 -0
  30. dbt/fal/adapters/python/connections.py +319 -0
  31. dbt/fal/adapters/python/impl.py +291 -0
  32. dbt/fal/adapters/teleport/__init__.py +3 -0
  33. dbt/fal/adapters/teleport/impl.py +103 -0
  34. dbt/fal/adapters/teleport/info.py +73 -0
  35. dbt/include/fal/__init__.py +3 -0
  36. dbt/include/fal/dbt_project.yml +5 -0
  37. dbt/include/fal/macros/materializations/table.sql +46 -0
  38. dbt/include/fal/macros/teleport_duckdb.sql +8 -0
  39. dbt/include/fal/macros/teleport_snowflake.sql +31 -0
  40. dbt/include/fal_experimental/__init__.py +3 -0
  41. dbt/include/fal_experimental/dbt_project.yml +5 -0
  42. dbt/include/fal_experimental/macros/materializations/table.sql +36 -0
  43. fal/__init__.py +61 -11
  44. fal/dbt/__init__.py +11 -0
  45. fal/dbt/cli/__init__.py +1 -0
  46. fal/{cli → dbt/cli}/args.py +7 -2
  47. fal/{cli → dbt/cli}/cli.py +18 -3
  48. fal/{cli → dbt/cli}/dbt_runner.py +1 -1
  49. fal/{cli → dbt/cli}/fal_runner.py +6 -6
  50. fal/{cli → dbt/cli}/flow_runner.py +9 -9
  51. fal/{cli → dbt/cli}/model_generator/model_generator.py +5 -5
  52. fal/{cli → dbt/cli}/selectors.py +2 -2
  53. fal/{fal_script.py → dbt/fal_script.py} +4 -4
  54. {faldbt → fal/dbt/integration}/lib.py +2 -2
  55. {faldbt → fal/dbt/integration}/magics.py +2 -2
  56. {faldbt → fal/dbt/integration}/parse.py +7 -7
  57. {faldbt → fal/dbt/integration}/project.py +7 -7
  58. fal/dbt/integration/utils/yaml_helper.py +80 -0
  59. fal/dbt/new/project.py +43 -0
  60. fal/{node_graph.py → dbt/node_graph.py} +2 -2
  61. fal/{packages → dbt/packages}/dependency_analysis.py +32 -38
  62. fal/{packages → dbt/packages}/environments/__init__.py +3 -3
  63. fal/{packages → dbt/packages}/environments/base.py +2 -2
  64. fal/{packages → dbt/packages}/environments/conda.py +3 -3
  65. fal/{packages → dbt/packages}/environments/virtual_env.py +3 -3
  66. fal/{packages → dbt/packages}/isolated_runner.py +5 -5
  67. fal/{planner → dbt/planner}/executor.py +4 -4
  68. fal/{planner → dbt/planner}/plan.py +3 -3
  69. fal/{planner → dbt/planner}/schedule.py +5 -5
  70. fal/{planner → dbt/planner}/tasks.py +5 -5
  71. fal/{telemetry → dbt/telemetry}/telemetry.py +4 -4
  72. fal/{typing.py → dbt/typing.py} +2 -2
  73. fal/{utils.py → dbt/utils.py} +2 -2
  74. {fal-0.9.2.dist-info → fal-0.9.4.dist-info}/METADATA +98 -117
  75. fal-0.9.4.dist-info/RECORD +91 -0
  76. fal-0.9.4.dist-info/entry_points.txt +4 -0
  77. fal/cli/__init__.py +0 -1
  78. fal-0.9.2.dist-info/RECORD +0 -47
  79. fal-0.9.2.dist-info/entry_points.txt +0 -3
  80. {faldbt → dbt/adapters/fal_experimental}/utils/yaml_helper.py +0 -0
  81. /fal/{cli → dbt/cli}/model_generator/__init__.py +0 -0
  82. /fal/{cli → dbt/cli}/model_generator/module_check.py +0 -0
  83. /fal/{feature_store → dbt/feature_store}/__init__.py +0 -0
  84. /fal/{feature_store → dbt/feature_store}/feature.py +0 -0
  85. /fal/{packages → dbt/integration}/__init__.py +0 -0
  86. {faldbt → fal/dbt/integration}/logger.py +0 -0
  87. /fal/{planner → dbt/integration/utils}/__init__.py +0 -0
  88. {faldbt → fal/dbt/integration}/version.py +0 -0
  89. /fal/{telemetry → dbt/packages}/__init__.py +0 -0
  90. /fal/{packages → dbt/packages}/bridge.py +0 -0
  91. {faldbt → fal/dbt/planner}/__init__.py +0 -0
  92. {faldbt/utils → fal/dbt/telemetry}/__init__.py +0 -0
  93. {fal-0.9.2.dist-info → fal-0.9.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,122 @@
1
+ from contextlib import contextmanager
2
+ from typing import Optional
3
+
4
+ from dbt.exceptions import DbtRuntimeError
5
+ from dbt.adapters.base.relation import BaseRelation
6
+ from dbt.adapters.base.impl import BaseAdapter
7
+
8
+ from dbt.adapters.fal_experimental.connections import (
9
+ TeleportCredentials,
10
+ TeleportTypeEnum,
11
+ )
12
+
13
+ from dbt.fal.adapters.teleport.impl import TeleportAdapter
14
+ from dbt.fal.adapters.teleport.info import TeleportInfo
15
+
16
+
17
+ class DuckDBAdapterTeleport(TeleportAdapter):
18
+ def __init__(
19
+ self, db_adapter: BaseAdapter, teleport_credentials: TeleportCredentials
20
+ ):
21
+ self._db_adapter = db_adapter
22
+ self.credentials = teleport_credentials
23
+ with self._db_adapter.connection_named("teleport:init"):
24
+ self._db_adapter.execute("INSTALL parquet")
25
+ self._db_adapter.execute("INSTALL httpfs")
26
+
27
+ @classmethod
28
+ def storage_formats(cls):
29
+ return ["parquet"]
30
+
31
+ def teleport_from_external_storage(
32
+ self, relation: BaseRelation, relation_path: str, teleport_info: TeleportInfo
33
+ ):
34
+ assert (
35
+ teleport_info.format == "parquet"
36
+ ), "duckdb only supports parquet format for Teleport"
37
+
38
+ url = teleport_info.build_url(relation_path)
39
+
40
+ with self._db_adapter.connection_named("teleport:copy_from"):
41
+ if self.credentials.type == TeleportTypeEnum.LOCAL:
42
+ rendered_macro = self._db_adapter.execute_macro(
43
+ "duckdb__copy_from_parquet",
44
+ kwargs={"relation": relation, "url": url},
45
+ )
46
+ self._db_adapter.execute(rendered_macro)
47
+
48
+ elif self.credentials.type == TeleportTypeEnum.REMOTE_S3:
49
+ with self._s3_setup():
50
+ rendered_macro = self._db_adapter.execute_macro(
51
+ "duckdb__copy_from_parquet",
52
+ kwargs={"relation": relation, "url": url},
53
+ )
54
+ self._db_adapter.execute(rendered_macro)
55
+ else:
56
+ raise RuntimeError(
57
+ f"Teleport type {self.credentials.type} not supported"
58
+ )
59
+
60
+ def teleport_to_external_storage(
61
+ self, relation: BaseRelation, teleport_info: TeleportInfo
62
+ ):
63
+ assert (
64
+ teleport_info.format == "parquet"
65
+ ), "duckdb only supports parquet format for Teleport"
66
+
67
+ rel_path = teleport_info.build_relation_path(relation)
68
+ url = teleport_info.build_url(rel_path)
69
+
70
+ with self._db_adapter.connection_named("teleport:copy_to"):
71
+ if self.credentials.type == TeleportTypeEnum.LOCAL:
72
+ rendered_macro = self._db_adapter.execute_macro(
73
+ "duckdb__copy_to", kwargs={"relation": relation, "url": url}
74
+ )
75
+ self._db_adapter.execute(rendered_macro)
76
+ elif self.credentials.type == TeleportTypeEnum.REMOTE_S3:
77
+ with self._s3_setup():
78
+ rendered_macro = self._db_adapter.execute_macro(
79
+ "duckdb__copy_to", kwargs={"relation": relation, "url": url}
80
+ )
81
+ self._db_adapter.execute(rendered_macro)
82
+ else:
83
+ raise RuntimeError(
84
+ f"Teleport type {self.credentials.type} not supported"
85
+ )
86
+
87
+ return rel_path
88
+
89
+ def _get_setting(self, name: str):
90
+ try:
91
+ _, table = self._db_adapter.execute(
92
+ f"SELECT current_setting('{name}')", fetch=True
93
+ )
94
+ return table.rows[0][0]
95
+ except DbtRuntimeError:
96
+ return None
97
+
98
+ def _set_setting(self, name: str, value: Optional[str]):
99
+ if value:
100
+ self._db_adapter.execute(f"SET {name} = '{value}'")
101
+ else:
102
+ # HACK while we get a response https://github.com/duckdb/duckdb/issues/4998
103
+ self._db_adapter.execute(f"SET {name} = ''")
104
+
105
+ @contextmanager
106
+ def _s3_setup(self):
107
+ self._db_adapter.execute("LOAD parquet")
108
+ self._db_adapter.execute("LOAD httpfs")
109
+
110
+ old_region = self._get_setting("s3_region")
111
+ old_access_key_id = self._get_setting("s3_access_key_id")
112
+ old_secret_access_key = self._get_setting("s3_secret_access_key")
113
+
114
+ self._set_setting("s3_region", self.credentials.s3_region)
115
+ self._set_setting("s3_access_key_id", self.credentials.s3_access_key_id)
116
+ self._set_setting("s3_secret_access_key", self.credentials.s3_access_key)
117
+
118
+ yield
119
+
120
+ self._set_setting("s3_region", old_region)
121
+ self._set_setting("s3_access_key_id", old_access_key_id)
122
+ self._set_setting("s3_secret_access_key", old_secret_access_key)
@@ -0,0 +1,72 @@
1
+ from dbt.adapters.fal_experimental.connections import TeleportCredentials
2
+ from dbt.fal.adapters.teleport.impl import TeleportAdapter
3
+ from dbt.fal.adapters.teleport.info import TeleportInfo
4
+ from dbt.adapters.base.relation import BaseRelation
5
+ from dbt.adapters.base.impl import BaseAdapter
6
+
7
+
8
+ class SnowflakeAdapterTeleport(TeleportAdapter):
9
+
10
+ def __init__(self, db_adapter: BaseAdapter, teleport_credentials: TeleportCredentials):
11
+ from dbt.adapters.fal_experimental.adapter_support import new_connection
12
+ self._db_adapter = db_adapter
13
+ self._credentials = teleport_credentials
14
+
15
+ # TODO: put this in teleport_info
16
+ url = f's3://{teleport_credentials.s3_bucket}/teleport'
17
+
18
+ with new_connection(self._db_adapter, "fal-snowflake:setup-teleport") as conn:
19
+ cur = conn.handle.cursor()
20
+
21
+ create_stage_query = f"""CREATE OR REPLACE STAGE falstage
22
+ URL = '{url}' CREDENTIALS = (
23
+ aws_key_id='{self._credentials.s3_access_key_id}',
24
+ aws_secret_key='{self._credentials.s3_access_key}');"""
25
+
26
+ create_format_query = """CREATE OR REPLACE FILE FORMAT falparquet type = 'PARQUET';"""
27
+
28
+ cur.execute(create_stage_query)
29
+ cur.execute(create_format_query)
30
+
31
+
32
+ @classmethod
33
+ def storage_formats(cls):
34
+ return ['parquet']
35
+
36
+ def teleport_from_external_storage(self, relation: BaseRelation, relation_path: str, teleport_info: TeleportInfo) -> None:
37
+ assert teleport_info.format == 'parquet', "snowflake only supports parquet format for Teleport"
38
+ location = f"@falstage/{relation_path}"
39
+
40
+ with self._db_adapter.connection_named('teleport:copy_from'):
41
+ create_macro = self._db_adapter.execute_macro(
42
+ 'snowflake__create_table_from_parquet',
43
+ kwargs={
44
+ 'relation': relation,
45
+ 'location': location,
46
+ })
47
+
48
+ self._db_adapter.execute(create_macro)
49
+
50
+ copy_macro = self._db_adapter.execute_macro(
51
+ 'snowflake__copy_from_parquet',
52
+ kwargs={
53
+ 'relation': relation,
54
+ 'location': location,
55
+ }
56
+ )
57
+ self._db_adapter.execute(copy_macro)
58
+
59
+ def teleport_to_external_storage(self, relation: BaseRelation, teleport_info: TeleportInfo) -> str:
60
+ assert teleport_info.format == 'parquet', "snowflake only supports parquet format for Teleport"
61
+ relation_path = teleport_info.build_relation_path(relation)
62
+ location = f"@falstage/{relation_path}"
63
+ rendered_macro = self._db_adapter.execute_macro(
64
+ 'snowflake__copy_to_parquet',
65
+ kwargs={
66
+ 'relation': relation,
67
+ 'location': location
68
+ })
69
+ with self._db_adapter.connection_named('teleport:copy_to'):
70
+ self._db_adapter.execute(rendered_macro)
71
+ return relation_path
72
+
@@ -0,0 +1,50 @@
1
+ from contextlib import contextmanager
2
+ from typing import Any
3
+
4
+ from dbt.config.runtime import RuntimeConfig
5
+
6
+ try:
7
+ from functools import lru_cache
8
+ except ImportError:
9
+ from backports.functools_lru_cache import lru_cache
10
+
11
+
12
+ FAL_SCRIPTS_PATH_VAR_NAME = 'fal-scripts-path'
13
+
14
+
15
+ def cache_static(func):
16
+ """Cache the result of a function."""
17
+ return lru_cache(maxsize=None)(func)
18
+
19
+
20
+ def retrieve_symbol(source_code: str, symbol_name: str) -> Any:
21
+ """Retrieve the function with the given name from the source code."""
22
+ namespace = {}
23
+ exec(source_code, namespace)
24
+ return namespace[symbol_name]
25
+
26
+
27
+ def get_fal_scripts_path(config: RuntimeConfig):
28
+ import pathlib
29
+ project_path = pathlib.Path(config.project_root)
30
+
31
+ # Default value
32
+ fal_scripts_path = ''
33
+
34
+ if hasattr(config, 'vars'):
35
+ fal_scripts_path: str = config.vars.to_dict().get(FAL_SCRIPTS_PATH_VAR_NAME, fal_scripts_path) # type: ignore
36
+
37
+ if hasattr(config, 'cli_vars'):
38
+ fal_scripts_path = config.cli_vars.get(FAL_SCRIPTS_PATH_VAR_NAME, fal_scripts_path)
39
+
40
+ return project_path / fal_scripts_path
41
+
42
+
43
+ @contextmanager
44
+ def extra_path(path: str):
45
+ import sys
46
+ sys.path.append(path)
47
+ try:
48
+ yield
49
+ finally:
50
+ sys.path.remove(path)
@@ -0,0 +1,302 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, replace
4
+ from pathlib import Path
5
+ from typing import Any, ClassVar, Dict, Iterator, List, Optional, Tuple
6
+ import importlib_metadata
7
+
8
+ from dbt.events.adapter_endpoint import AdapterLogger
9
+ from dbt.exceptions import DbtRuntimeError
10
+ from dbt.config.runtime import RuntimeConfig
11
+
12
+ from isolate.backends import BaseEnvironment, BasicCallable, EnvironmentConnection
13
+ from fal_serverless import FalServerlessKeyCredentials, LocalHost
14
+ from fal_serverless.api import Host, FalServerlessHost
15
+
16
+ from . import cache_static
17
+ from .yaml_helper import load_yaml
18
+
19
+
20
+ CONFIG_KEYS_TO_IGNORE = ["host", "remote_type", "type", "name", "machine_type"]
21
+ REMOTE_TYPES_DICT = {
22
+ "venv": "virtualenv",
23
+ "conda": "conda",
24
+ }
25
+
26
+ logger = AdapterLogger("fal")
27
+
28
+
29
+ class FalParseError(Exception):
30
+ pass
31
+
32
+
33
+ @dataclass
34
+ class LocalConnection(EnvironmentConnection):
35
+ def run(self, executable: BasicCallable, *args, **kwargs) -> Any:
36
+ return executable(*args, **kwargs)
37
+
38
+
39
+ @dataclass
40
+ class EnvironmentDefinition():
41
+ host: Host
42
+ kind: str
43
+ config: dict[Any, Any]
44
+ machine_type: str = "S"
45
+
46
+
47
+ def fetch_environment(
48
+ project_root: str,
49
+ environment_name: str,
50
+ machine_type: str = "S",
51
+ credentials: Optional[Any] = None,
52
+ ) -> Tuple[EnvironmentDefinition, bool]:
53
+ """Fetch the environment with the given name from the project's
54
+ fal_project.yml file."""
55
+ # Local is a special environment where it doesn't need to be defined
56
+ # since it will mirror user's execution context directly.
57
+ if environment_name == "local":
58
+ if credentials.host:
59
+ logger.warning(
60
+ "`local` environments will be executed on fal cloud."
61
+ + "If you don't want to use fal cloud, you can change your "
62
+ + "profile target to one where fal doesn't have credentials"
63
+ )
64
+ host = FalServerlessHost(
65
+ url=credentials.host,
66
+ credentials=FalServerlessKeyCredentials(credentials.key_id, credentials.key_secret))
67
+ return EnvironmentDefinition(
68
+ host=host,
69
+ kind="virtualenv",
70
+ machine_type=machine_type,
71
+ config={"name": "", "type": "venv"}), False
72
+
73
+ return EnvironmentDefinition(host=LocalHost(), kind="local", config={}), True
74
+
75
+ try:
76
+ environments = load_environments(project_root, machine_type, credentials)
77
+ except Exception as exc:
78
+ raise DbtRuntimeError(
79
+ "Error loading environments from fal_project.yml"
80
+ ) from exc
81
+
82
+ if environment_name not in environments:
83
+ raise DbtRuntimeError(
84
+ f"Environment '{environment_name}' was used but not defined in fal_project.yml"
85
+ )
86
+
87
+ return environments[environment_name], False
88
+
89
+
90
+ def db_adapter_config(config: RuntimeConfig) -> RuntimeConfig:
91
+ """Return a config object that has the database adapter as its primary. Only
92
+ applicable when the underlying db adapter is encapsulated."""
93
+ if hasattr(config, "sql_adapter_credentials"):
94
+ new_config = replace(config, credentials=config.sql_adapter_credentials)
95
+ new_config.python_adapter_credentials = config.credentials
96
+ else:
97
+ new_config = config
98
+
99
+ return new_config
100
+
101
+
102
+ def load_environments(
103
+ base_dir: str, machine_type: str = "S", credentials: Optional[Any] = None
104
+ ) -> Dict[str, EnvironmentDefinition]:
105
+ import os
106
+ fal_project_path = os.path.join(base_dir, "fal_project.yml")
107
+ if not os.path.exists(fal_project_path):
108
+ raise FalParseError(f"{fal_project_path} must exist to define environments")
109
+
110
+ fal_project = load_yaml(fal_project_path)
111
+ environments = {}
112
+ for environment in fal_project.get("environments", []):
113
+ env_name = _get_required_key(environment, "name")
114
+ if _is_local_environment(env_name):
115
+ raise FalParseError(
116
+ f"Environment name conflicts with a reserved name: {env_name}."
117
+ )
118
+
119
+ env_kind = _get_required_key(environment, "type")
120
+ if environments.get(env_name) is not None:
121
+ raise FalParseError("Environment names must be unique.")
122
+
123
+ environments[env_name] = create_environment(
124
+ env_name, env_kind, environment, machine_type, credentials
125
+ )
126
+
127
+ return environments
128
+
129
+
130
+ def create_environment(
131
+ name: str,
132
+ kind: str,
133
+ config: Dict[str, Any],
134
+ machine_type: str = "S",
135
+ credentials: Optional[Any] = None,
136
+ ) -> EnvironmentDefinition:
137
+ if kind not in ["venv", "conda"]:
138
+ raise ValueError(
139
+ f"Invalid environment type (of {kind}) for {name}. Please choose from: "
140
+ + "venv, conda."
141
+ )
142
+
143
+ kind = kind if kind == "conda" else "virtualenv"
144
+
145
+ parsed_config = {
146
+ key: val for key, val in config.items() if key not in CONFIG_KEYS_TO_IGNORE
147
+ }
148
+
149
+ if credentials.key_secret and credentials.key_id:
150
+ host = FalServerlessHost(
151
+ url=credentials.host,
152
+ credentials=FalServerlessKeyCredentials(credentials.key_id, credentials.key_secret))
153
+ else:
154
+ host = LocalHost()
155
+ return EnvironmentDefinition(
156
+ host=host,
157
+ kind=kind,
158
+ config=parsed_config,
159
+ machine_type=machine_type)
160
+
161
+
162
+ def _is_local_environment(environment_name: str) -> bool:
163
+ return environment_name == "local"
164
+
165
+
166
+ def _get_required_key(data: Dict[str, Any], name: str) -> Any:
167
+ if name not in data:
168
+ raise FalParseError("Missing required key: " + name)
169
+ return data[name]
170
+
171
+ def _parse_remote_config(
172
+ config: Dict[str, Any], parsed_config: Dict[str, Any]
173
+ ) -> Dict[str, Any]:
174
+ assert config.get("remote_type"), "remote_type needs to be specified."
175
+
176
+ remote_type = REMOTE_TYPES_DICT.get(config["remote_type"])
177
+
178
+ assert (
179
+ remote_type
180
+ ), f"{config['remote_type']} not recognised. Available remote types: {list(REMOTE_TYPES_DICT.keys())}"
181
+
182
+ env_definition = {
183
+ "kind": remote_type,
184
+ "configuration": parsed_config,
185
+ }
186
+
187
+ return {
188
+ "host": config.get("host"),
189
+ "target_environments": [env_definition],
190
+ }
191
+
192
+ def _get_package_from_type(adapter_type: str):
193
+ SPECIAL_ADAPTERS = {
194
+ # Documented in https://docs.getdbt.com/docs/supported-data-platforms#community-adapters
195
+ "athena": "dbt-athena-community",
196
+ }
197
+ return SPECIAL_ADAPTERS.get(adapter_type, f"dbt-{adapter_type}")
198
+
199
+ def _get_dbt_packages(
200
+ adapter_type: str,
201
+ is_teleport: bool = False,
202
+ is_remote: bool = False,
203
+ ) -> Iterator[Tuple[str, Optional[str]]]:
204
+ dbt_adapter = _get_package_from_type(adapter_type)
205
+ for dbt_plugin_name in [dbt_adapter]:
206
+ distribution = importlib_metadata.distribution(dbt_plugin_name)
207
+
208
+ yield dbt_plugin_name, distribution.version
209
+
210
+ try:
211
+ dbt_fal_version = importlib_metadata.version("dbt-fal")
212
+ except importlib_metadata.PackageNotFoundError:
213
+ # It might not be installed.
214
+ return None
215
+
216
+ dbt_fal_dep = "dbt-fal"
217
+ dbt_fal_extras = _find_adapter_extras(dbt_fal_dep, dbt_adapter)
218
+ if is_teleport:
219
+ dbt_fal_extras.add("teleport")
220
+ dbt_fal_suffix = ""
221
+
222
+ if _version_is_prerelease(dbt_fal_version):
223
+ if is_remote:
224
+ # If it's a pre-release and it's remote, its likely us developing, so we try installing
225
+ # from Github and we can get the custom branch name from FAL_GITHUB_BRANCH environment variable
226
+ # TODO: Handle pre-release on PyPI. How should we approach that?
227
+ import os
228
+
229
+ branch_name = os.environ.get("FAL_GITHUB_BRANCH", "main")
230
+
231
+ dbt_fal_suffix = f" @ git+https://github.com/fal-ai/fal.git@{branch_name}#subdirectory=projects/adapter"
232
+ dbt_fal_version = None
233
+ else:
234
+ dbt_fal_path = _get_project_root_path("adapter")
235
+ if dbt_fal_path is not None:
236
+ # Can be a pre-release from PyPI
237
+ dbt_fal_dep = str(dbt_fal_path)
238
+ dbt_fal_version = None
239
+
240
+ dbt_fal = f"{dbt_fal_dep}[{' ,'.join(dbt_fal_extras)}]{dbt_fal_suffix}"
241
+ yield dbt_fal, dbt_fal_version
242
+
243
+
244
+ def _find_adapter_extras(package: str, plugin_package: str) -> set[str]:
245
+ import pkgutil
246
+ import dbt.adapters
247
+
248
+ all_extras = _get_extras(package)
249
+ available_plugins = {
250
+ module_info.name
251
+ for module_info in pkgutil.iter_modules(dbt.adapters.__path__)
252
+ if module_info.ispkg and module_info.name in plugin_package
253
+ }
254
+ return available_plugins.intersection(all_extras)
255
+
256
+
257
+ def _get_extras(package: str) -> list[str]:
258
+ import importlib_metadata
259
+
260
+ dist = importlib_metadata.distribution(package)
261
+ return dist.metadata.get_all("Provides-Extra", [])
262
+
263
+
264
+ def _version_is_prerelease(raw_version: str) -> bool:
265
+ from packaging.version import Version
266
+
267
+ package_version = Version(raw_version)
268
+ return package_version.is_prerelease
269
+
270
+
271
+ def _get_project_root_path(package: str) -> Path:
272
+ from dbt.adapters import fal
273
+
274
+ # If this is a development version, we'll install
275
+ # the current fal itself.
276
+ path = Path(fal.__file__)
277
+ while path is not None:
278
+ if (path.parent / ".git").exists():
279
+ break
280
+ path = path.parent
281
+ return path / package
282
+
283
+
284
+ def get_default_requirements(
285
+ adapter_type: str,
286
+ is_teleport: bool = False,
287
+ is_remote: bool = False,
288
+ ) -> Iterator[Tuple[str, Optional[str]]]:
289
+ yield from _get_dbt_packages(adapter_type, is_teleport, is_remote)
290
+
291
+ @cache_static
292
+ def get_default_pip_dependencies(
293
+ adapter_type: str,
294
+ is_teleport: bool = False,
295
+ is_remote: bool = False,
296
+ ) -> List[str]:
297
+ return [
298
+ f"{package}=={version}" if version else package
299
+ for package, version in get_default_requirements(
300
+ adapter_type, is_teleport, is_remote
301
+ )
302
+ ]
@@ -0,0 +1,3 @@
1
+ # these are all just exports, #noqa them so flake8 will be happy
2
+ from dbt.fal.adapters.python.connections import PythonConnectionManager # noqa
3
+ from dbt.fal.adapters.python.impl import PythonAdapter # noqa