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.
- _fal_testing/utils.py +2 -2
- dbt/adapters/fal/__init__.py +21 -0
- dbt/adapters/fal/__version__.py +1 -0
- dbt/adapters/fal/connections.py +18 -0
- dbt/adapters/fal/impl.py +93 -0
- dbt/adapters/fal/load_db_profile.py +80 -0
- dbt/adapters/fal/wrappers.py +113 -0
- dbt/adapters/fal_experimental/__init__.py +11 -0
- dbt/adapters/fal_experimental/__version__.py +1 -0
- dbt/adapters/fal_experimental/adapter.py +149 -0
- dbt/adapters/fal_experimental/adapter_support.py +234 -0
- dbt/adapters/fal_experimental/connections.py +72 -0
- dbt/adapters/fal_experimental/impl.py +240 -0
- dbt/adapters/fal_experimental/support/athena.py +92 -0
- dbt/adapters/fal_experimental/support/bigquery.py +74 -0
- dbt/adapters/fal_experimental/support/duckdb.py +28 -0
- dbt/adapters/fal_experimental/support/postgres.py +88 -0
- dbt/adapters/fal_experimental/support/redshift.py +56 -0
- dbt/adapters/fal_experimental/support/snowflake.py +76 -0
- dbt/adapters/fal_experimental/support/trino.py +26 -0
- dbt/adapters/fal_experimental/telemetry/__init__.py +1 -0
- dbt/adapters/fal_experimental/telemetry/telemetry.py +411 -0
- dbt/adapters/fal_experimental/teleport.py +192 -0
- dbt/adapters/fal_experimental/teleport_adapter_support.py +23 -0
- dbt/adapters/fal_experimental/teleport_support/duckdb.py +122 -0
- dbt/adapters/fal_experimental/teleport_support/snowflake.py +72 -0
- dbt/adapters/fal_experimental/utils/__init__.py +50 -0
- dbt/adapters/fal_experimental/utils/environments.py +302 -0
- dbt/fal/adapters/python/__init__.py +3 -0
- dbt/fal/adapters/python/connections.py +319 -0
- dbt/fal/adapters/python/impl.py +291 -0
- dbt/fal/adapters/teleport/__init__.py +3 -0
- dbt/fal/adapters/teleport/impl.py +103 -0
- dbt/fal/adapters/teleport/info.py +73 -0
- dbt/include/fal/__init__.py +3 -0
- dbt/include/fal/dbt_project.yml +5 -0
- dbt/include/fal/macros/materializations/table.sql +46 -0
- dbt/include/fal/macros/teleport_duckdb.sql +8 -0
- dbt/include/fal/macros/teleport_snowflake.sql +31 -0
- dbt/include/fal_experimental/__init__.py +3 -0
- dbt/include/fal_experimental/dbt_project.yml +5 -0
- dbt/include/fal_experimental/macros/materializations/table.sql +36 -0
- fal/__init__.py +61 -11
- fal/dbt/__init__.py +11 -0
- fal/dbt/cli/__init__.py +1 -0
- fal/{cli → dbt/cli}/args.py +7 -2
- fal/{cli → dbt/cli}/cli.py +18 -3
- fal/{cli → dbt/cli}/dbt_runner.py +1 -1
- fal/{cli → dbt/cli}/fal_runner.py +6 -6
- fal/{cli → dbt/cli}/flow_runner.py +9 -9
- fal/{cli → dbt/cli}/model_generator/model_generator.py +5 -5
- fal/{cli → dbt/cli}/selectors.py +2 -2
- fal/{fal_script.py → dbt/fal_script.py} +4 -4
- {faldbt → fal/dbt/integration}/lib.py +2 -2
- {faldbt → fal/dbt/integration}/magics.py +2 -2
- {faldbt → fal/dbt/integration}/parse.py +7 -7
- {faldbt → fal/dbt/integration}/project.py +7 -7
- fal/dbt/integration/utils/yaml_helper.py +80 -0
- fal/dbt/new/project.py +43 -0
- fal/{node_graph.py → dbt/node_graph.py} +2 -2
- fal/{packages → dbt/packages}/dependency_analysis.py +32 -38
- fal/{packages → dbt/packages}/environments/__init__.py +3 -3
- fal/{packages → dbt/packages}/environments/base.py +2 -2
- fal/{packages → dbt/packages}/environments/conda.py +3 -3
- fal/{packages → dbt/packages}/environments/virtual_env.py +3 -3
- fal/{packages → dbt/packages}/isolated_runner.py +5 -5
- fal/{planner → dbt/planner}/executor.py +4 -4
- fal/{planner → dbt/planner}/plan.py +3 -3
- fal/{planner → dbt/planner}/schedule.py +5 -5
- fal/{planner → dbt/planner}/tasks.py +5 -5
- fal/{telemetry → dbt/telemetry}/telemetry.py +4 -4
- fal/{typing.py → dbt/typing.py} +2 -2
- fal/{utils.py → dbt/utils.py} +2 -2
- {fal-0.9.2.dist-info → fal-0.9.4.dist-info}/METADATA +98 -117
- fal-0.9.4.dist-info/RECORD +91 -0
- fal-0.9.4.dist-info/entry_points.txt +4 -0
- fal/cli/__init__.py +0 -1
- fal-0.9.2.dist-info/RECORD +0 -47
- fal-0.9.2.dist-info/entry_points.txt +0 -3
- {faldbt → dbt/adapters/fal_experimental}/utils/yaml_helper.py +0 -0
- /fal/{cli → dbt/cli}/model_generator/__init__.py +0 -0
- /fal/{cli → dbt/cli}/model_generator/module_check.py +0 -0
- /fal/{feature_store → dbt/feature_store}/__init__.py +0 -0
- /fal/{feature_store → dbt/feature_store}/feature.py +0 -0
- /fal/{packages → dbt/integration}/__init__.py +0 -0
- {faldbt → fal/dbt/integration}/logger.py +0 -0
- /fal/{planner → dbt/integration/utils}/__init__.py +0 -0
- {faldbt → fal/dbt/integration}/version.py +0 -0
- /fal/{telemetry → dbt/packages}/__init__.py +0 -0
- /fal/{packages → dbt/packages}/bridge.py +0 -0
- {faldbt → fal/dbt/planner}/__init__.py +0 -0
- {faldbt/utils → fal/dbt/telemetry}/__init__.py +0 -0
- {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
|
+
]
|