Skip to content

Commit

Permalink
[Feature] Enable cross project dbt ref support, dbt mesh, `multi po…
Browse files Browse the repository at this point in the history
…rject dbt setup` (#49)

* Enable multi project references and settings

Revert "Enable multi project references and settings"

This reverts commit 2686a19.

* Enable multi project references and settings

* Enable multi project references and settings
  • Loading branch information
ismailsimsek authored Dec 16, 2024
1 parent 7e91e6b commit 4bede65
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 1 deletion.
4 changes: 4 additions & 0 deletions opendbt/dbt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ def patch_dbt():
# ================================================================================================================
dbt_version = Version(version.get_installed_version().to_version_string(skip_matcher=True))
if Version("1.6.0") <= dbt_version < Version("1.8.0"):
from opendbt.dbt.v17.config.runtime import OpenDbtRuntimeConfig
dbt.config.RuntimeConfig = OpenDbtRuntimeConfig
from opendbt.dbt.v17.task.docs.generate import OpenDbtGenerateTask
dbt.task.generate.GenerateTask = OpenDbtGenerateTask
from opendbt.dbt.v17.adapters.factory import OpenDbtAdapterContainer
dbt.adapters.factory.FACTORY = OpenDbtAdapterContainer()
from opendbt.dbt.v17.task.run import ModelRunner
dbt.task.run.ModelRunner = ModelRunner
elif Version("1.8.0") <= dbt_version < Version("1.10.0"):
from opendbt.dbt.v18.config.runtime import OpenDbtRuntimeConfig
dbt.config.RuntimeConfig = OpenDbtRuntimeConfig
from opendbt.dbt.v18.task.docs.generate import OpenDbtGenerateTask
dbt.task.docs.generate.GenerateTask = OpenDbtGenerateTask
from opendbt.dbt.v18.adapters.factory import OpenDbtAdapterContainer
Expand Down
Empty file.
58 changes: 58 additions & 0 deletions opendbt/dbt/v17/config/runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping

from dbt.config import RuntimeConfig
from dbt.config.project import path_exists, _load_yaml
from dbt.constants import DEPENDENCIES_FILE_NAME
from dbt.exceptions import (
DbtProjectError, NonUniquePackageNameError,
)
from typing_extensions import override


def load_yml_dict(file_path):
ret = {}
if path_exists(file_path):
ret = _load_yaml(file_path) or {}
return ret

# pylint: disable=too-many-ancestors
@dataclass
class OpenDbtRuntimeConfig(RuntimeConfig):
def load_dependence_projects(self):
dependencies_yml_dict = load_yml_dict(f"{self.project_root}/{DEPENDENCIES_FILE_NAME}")

if "projects" not in dependencies_yml_dict:
return

projects = dependencies_yml_dict["projects"]
project_root_parent = Path(self.project_root).parent
for project in projects:
path = project_root_parent.joinpath(project['name'])
try:
project = self.new_project(str(path.as_posix()))
except DbtProjectError as e:
raise DbtProjectError(
f"Failed to read depending project: {e} \n project path:{path.as_posix()}",
result_type="invalid_project",
path=path,
) from e

yield project.project_name, project

@override
def load_dependencies(self, base_only=False) -> Mapping[str, "RuntimeConfig"]:
# if self.dependencies is None:

if self.dependencies is None:
# this sets self.dependencies variable!
self.dependencies = super().load_dependencies(base_only=base_only)

# additionally load `projects` defined in `dependencies.yml`
for project_name, project in self.load_dependence_projects():
if project_name in self.dependencies:
raise NonUniquePackageNameError(project_name)
self.dependencies[project_name] = project

return self.dependencies
Empty file.
52 changes: 52 additions & 0 deletions opendbt/dbt/v18/config/runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping

from dbt.config import RuntimeConfig
from dbt.config.project import load_yml_dict
from dbt.constants import DEPENDENCIES_FILE_NAME
from dbt.exceptions import (
DbtProjectError, NonUniquePackageNameError,
)
from typing_extensions import override


# pylint: disable=too-many-ancestors
@dataclass
class OpenDbtRuntimeConfig(RuntimeConfig):
def load_dependence_projects(self):
dependencies_yml_dict = load_yml_dict(f"{self.project_root}/{DEPENDENCIES_FILE_NAME}")

if "projects" not in dependencies_yml_dict:
return

projects = dependencies_yml_dict["projects"]
project_root_parent = Path(self.project_root).parent
for project in projects:
path = project_root_parent.joinpath(project['name'])
try:
project = self.new_project(str(path.as_posix()))
except DbtProjectError as e:
raise DbtProjectError(
f"Failed to read depending project: {e} \n project path:{path.as_posix()}",
result_type="invalid_project",
path=path,
) from e

yield project.project_name, project

@override
def load_dependencies(self, base_only=False) -> Mapping[str, "RuntimeConfig"]:
# if self.dependencies is None:

if self.dependencies is None:
# this sets self.dependencies variable!
self.dependencies = super().load_dependencies(base_only=base_only)

# additionally load `projects` defined in `dependencies.yml`
for project_name, project in self.load_dependence_projects():
if project_name in self.dependencies:
raise NonUniquePackageNameError(project_name)
self.dependencies[project_name] = project

return self.dependencies
2 changes: 1 addition & 1 deletion tests/resources/dbtcore/profiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ dbtcore:
dev:
type: duckdb
adapter: my.dbt.custom.OpenAdapterXXX
path: dev.duckdb
path: ./../dev.duckdb
threads: 1

prod:
Expand Down
4 changes: 4 additions & 0 deletions tests/resources/dbtfinance/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

target/
dbt_packages/
logs/
9 changes: 9 additions & 0 deletions tests/resources/dbtfinance/dbt_project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: 'dbtfinance'
version: '1.0.0'

profile: 'dbtfinance'

# directories to be removed by `dbt clean`
clean-targets:
- "target"
- "dbt_packages"
7 changes: 7 additions & 0 deletions tests/resources/dbtfinance/dependencies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#packages:
# - package: dbt-labs/dbt_utils
# version: 1.1.1

# case-sensitive and matches the 'name' in the 'dbt_project.yml'
projects:
- name: dbtcore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

select * from {{ ref('dbtcore', 'my_core_table1') }}
15 changes: 15 additions & 0 deletions tests/resources/dbtfinance/profiles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
dbtfinance:
outputs:
dev:
type: duckdb
adapter: my.dbt.custom.OpenAdapterXXX
path: ./../dev.duckdb
threads: 1

prod:
type: duckdb
adapter: my.dbt.custom.OpenAdapterXXX
path: prod.duckdb
threads: 4

target: dev
6 changes: 6 additions & 0 deletions tests/test_opendbt_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ def test_cli_run_models(self):
dp = OpenDbtCli(project_dir=self.DBTCORE_DIR)
dp.invoke(args=['run', '--select', 'my_first_dbt_model+', "--exclude", "my_failing_dbt_model", "--profiles-dir",
dp.project_dir.as_posix()])

def test_cli_run_cross_project_ref_models(self):
dpf = OpenDbtCli(project_dir=self.DBTFINANCE_DIR)
dpc = OpenDbtCli(project_dir=self.DBTCORE_DIR)
dpc.invoke(args=['run', '--select', 'my_core_table1', "--profiles-dir", dpc.project_dir.as_posix()])
dpf.invoke(args=['run', '--select', 'my_cross_project_ref_model', "--profiles-dir", dpf.project_dir.as_posix()])

0 comments on commit 4bede65

Please sign in to comment.