Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/83 official api support #91

Merged
merged 19 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 10 additions & 3 deletions .github/workflows/publish-docs-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ jobs:
key: ${{ github.ref }}
path: .cache

- uses: abatilo/actions-poetry@v2
with:
poetry-version: 1.6.1

- name: Install dependencies
run: pip install mkdocs mkdocs-material mkdocs-minify-plugin mike
run: |
poetry config installer.max-workers 1
poetry config virtualenvs.in-project true
poetry install

- name: Configure Git user
run: |
Expand All @@ -41,9 +48,9 @@ jobs:
- name: Publish site version (non-stable)
if: ${{ inputs.delete == 'false' }}
run: |
mike deploy --push --update-aliases ${{ inputs.version }}
poetry run mike deploy --push --update-aliases ${{ inputs.version }}

- name: Delete site version
if: ${{ inputs.delete == 'true' }}
run: |
mike delete --push ${{ inputs.version }}
poetry run mike delete --push ${{ inputs.version }}
11 changes: 9 additions & 2 deletions .github/workflows/publish-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ jobs:
key: ${{ github.ref }}
path: .cache

- uses: abatilo/actions-poetry@v2
with:
poetry-version: 1.6.1

- name: Install dependencies
run: pip install mkdocs mkdocs-material mkdocs-minify-plugin mike
run: |
poetry config installer.max-workers 1
poetry config virtualenvs.in-project true
poetry install

- name: Configure Git user
run: |
Expand All @@ -32,4 +39,4 @@ jobs:

- name: Publish site
run: |
mike deploy --push --update-aliases ${{ inputs.version }} latest
poetry run mike deploy --push --update-aliases ${{ inputs.version }} latest
22 changes: 21 additions & 1 deletion dbterd/adapters/adapter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from importlib import import_module


def load_executor(name: str):
def load_target(name: str):
"""Import the target extension dynamically

Args:
Expand All @@ -19,3 +19,23 @@ def load_executor(name: str):
if exc.name == "dbterd.adapters.targets." + name:
raise Exception(f"Could not find adapter target type {name}!")
raise # pragma: no cover


def load_algo(name: str):
"""Import the algo extension dynamically

Args:
name (str): Algo name e.g. test_relationship

Raises:
Exception: Algo not found

Returns:
ModuleType: Imported module
"""
try:
return import_module(name=f".{name}", package="dbterd.adapters.algos")
except ModuleNotFoundError as exc:
if exc.name == "dbterd.adapters.algos." + name:
raise Exception(f"Could not find adapter algo {name}!")
raise # pragma: no cover
31 changes: 26 additions & 5 deletions dbterd/adapters/algos/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,30 @@ def get_relationships_from_metadata(data=[], **kwargs) -> List[Ref]:
return get_unique_refs(refs=refs)


def get_test_nodes_by_rule_name(manifest: Manifest, rule_name: str) -> List:
"""Get manifest nodes given the algo rule name.

Default algo rule name is `relationship`,
see `get_algo_rule` function for more details.

Args:
rule_name (str): Rule name
manifest (Manifest): Manifest data

Returns:
List: List of manifest nodes
"""
return [
x
for x in manifest.nodes
if (
x.startswith("test")
and rule_name in x.lower()
and manifest.nodes[x].meta.get(TEST_META_IGNORE_IN_ERD, "0") == "0"
)
]


def get_relationships(manifest: Manifest, **kwargs) -> List[Ref]:
"""Extract relationships from dbt artifacts based on test relationship

Expand Down Expand Up @@ -456,11 +480,8 @@ def get_relationships(manifest: Manifest, **kwargs) -> List[Ref]:
manifest.nodes[x].meta.get(TEST_META_RELATIONSHIP_TYPE, "")
),
)
for x in manifest.nodes
if (
x.startswith("test")
and rule.get("name").lower() in x.lower()
and manifest.nodes[x].meta.get(TEST_META_IGNORE_IN_ERD, "0") == "0"
for x in get_test_nodes_by_rule_name(
manifest=manifest, rule_name=rule.get("name").lower()
)
]

Expand Down
36 changes: 36 additions & 0 deletions dbterd/adapters/algos/test_relationship.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import List, Tuple, Union

import click

from dbterd.adapters.algos import base
from dbterd.adapters.filter import is_selected_table
from dbterd.adapters.meta import Ref, Table
Expand Down Expand Up @@ -110,3 +112,37 @@ def parse(
sorted(tables, key=lambda tbl: tbl.node_name),
sorted(relationships, key=lambda rel: rel.name),
)


def find_related_nodes_by_id(
manifest: Union[Manifest, dict], node_unique_id: str, type: str = None, **kwargs
) -> List[str]:
"""Find the FK models which are related to the input model ID inclusively

given the manifest data of dbt project

Args:
manifest (Union[Manifest, dict]): Manifest data
node_unique_id (str): Manifest node unique ID
type (str, optional): Manifest type (local file or metadata). Defaults to None.

Raises:
click.BadParameter: Not Supported manifest type

Returns:
List[str]: Manifest nodes' unique ID
"""
if type is not None:
raise click.BadParameter("Not supported manifest type")

rule = base.get_algo_rule(**kwargs)
test_nodes = base.get_test_nodes_by_rule_name(
manifest=manifest, rule_name=rule.get("name").lower()
)
found_nodes = [node_unique_id]
for test_node in test_nodes:
nodes = manifest.nodes[test_node].depends_on.nodes or []
if node_unique_id in nodes:
found_nodes.extend(nodes)

return list(set(found_nodes))
64 changes: 54 additions & 10 deletions dbterd/adapters/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from pathlib import Path
from typing import List, Tuple

import click

Expand All @@ -9,6 +10,7 @@
from dbterd.adapters.dbt_cloud.discovery import DbtCloudMetadata
from dbterd.adapters.dbt_core.dbt_invocation import DbtInvocation
from dbterd.adapters.filter import has_unsupported_rule
from dbterd.adapters.meta import Ref, Table
from dbterd.helpers import cli_messaging
from dbterd.helpers import file as file_handlers
from dbterd.helpers.log import logger
Expand All @@ -26,15 +28,17 @@ def __init__(self, ctx) -> None:
self.filename_catalog = "catalog.json"
self.dbt: DbtInvocation = None

def run(self, **kwargs):
def run(
self, node_unique_id: str = None, **kwargs
) -> Tuple[List[Table], List[Ref]]:
"""Generate ERD from files"""
kwargs = self.evaluate_kwargs(**kwargs)
self.__run_by_strategy(**kwargs)
return self.__run_by_strategy(node_unique_id=node_unique_id, **kwargs)

def run_metadata(self, **kwargs):
def run_metadata(self, **kwargs) -> Tuple[List[Table], List[Ref]]:
"""Generate ERD from API metadata"""
kwargs = self.evaluate_kwargs(**kwargs)
self.__run_metadata_by_strategy(**kwargs)
return self.__run_metadata_by_strategy(**kwargs)

def evaluate_kwargs(self, **kwargs) -> dict:
"""Re-calculate the options
Expand Down Expand Up @@ -109,7 +113,7 @@ def __get_dir(self, **kwargs) -> str:

return (str(artifact_dir), str(project_dir))

def __get_selection(self, **kwargs):
def __get_selection(self, **kwargs) -> List[str]:
"""Override the Selection using dbt's one with `--dbt`"""
if not self.dbt:
raise click.UsageError("Flag `--dbt` need to be enabled")
Expand Down Expand Up @@ -154,7 +158,7 @@ def __get_operation(self, kwargs):
Returns:
func: Operation function
"""
target = adapter.load_executor(name=kwargs["target"]) # import {target}
target = adapter.load_target(name=kwargs["target"]) # import {target}
run_operation_dispatcher = getattr(target, "run_operation_dispatcher")
operation_default = getattr(target, "run_operation_default")
operation = run_operation_dispatcher.get(
Expand Down Expand Up @@ -182,7 +186,35 @@ def __save_result(self, path, data):
logger.error(str(e))
raise click.FileError(f"Could not save the output: {str(e)}")

def __run_by_strategy(self, **kwargs):
def __set_single_node_selection(
self, manifest, node_unique_id: str, type: str = None, **kwargs
) -> dict:
"""Override the Selection for the specific manifest node

Args:
manifest (Union[Manifest, dict]): Manifest data of dbt project
node_unique_id (str): Manifest node unique ID
type (str, optional): |
Determine manifest type e.g. from file or from metadata.
Defaults to None.

Returns:
dict: Editted kwargs dict
"""
if not node_unique_id:
return kwargs

algo_module = adapter.load_algo(name=kwargs["algo"])
kwargs["select"] = algo_module.find_related_nodes_by_id(
manifest=manifest, node_unique_id=node_unique_id, type=type, **kwargs
)
kwargs["exclude"] = []

return kwargs

def __run_by_strategy(
self, node_unique_id: str = None, **kwargs
) -> Tuple[List[Table], List[Ref]]:
"""Local File - Read artifacts and export the diagram file following the target"""
if kwargs.get("dbt_cloud"):
DbtCloudArtifact(**kwargs).get(artifacts_dir=kwargs.get("artifacts_dir"))
Expand All @@ -196,14 +228,26 @@ def __run_by_strategy(self, **kwargs):
cv=kwargs.get("catalog_version"),
)

if node_unique_id:
kwargs = self.__set_single_node_selection(
manifest=manifest, node_unique_id=node_unique_id, **kwargs
)
operation = self.__get_operation(kwargs)
result = operation(manifest=manifest, catalog=catalog, **kwargs)
self.__save_result(path=kwargs.get("output"), data=result)

def __run_metadata_by_strategy(self, **kwargs):
if not kwargs.get("api"):
self.__save_result(path=kwargs.get("output"), data=result)

return result

def __run_metadata_by_strategy(self, **kwargs) -> Tuple[List[Table], List[Ref]]:
"""Metadata - Read artifacts and export the diagram file following the target"""
data = DbtCloudMetadata(**kwargs).query_erd_data()
operation = self.__get_operation(kwargs)

result = operation(manifest=data, catalog="metadata", **kwargs)
self.__save_result(path=kwargs.get("output"), data=result)

if not kwargs.get("api"):
self.__save_result(path=kwargs.get("output"), data=result)

return result
Loading