Compare commits

...

5 Commits

8 changed files with 50 additions and 23 deletions

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Resolve relative local dependencies of local dependencies
time: 2024-08-23T15:03:43.292804-06:00
custom:
Author: dbeatty10
Issue: "10600"

View File

@@ -56,6 +56,7 @@ class Package(dbtClassMixin):
class LocalPackage(Package): class LocalPackage(Package):
local: str local: str
unrendered: Dict[str, Any] = field(default_factory=dict) unrendered: Dict[str, Any] = field(default_factory=dict)
project_root: Optional[str] = None
name: Optional[str] = None name: Optional[str] = None

View File

@@ -85,7 +85,7 @@ class PinnedPackage(BasePackage):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
def to_dict(self) -> Dict[str, str]: def to_dict(self) -> Dict[str, Optional[str]]:
raise NotImplementedError raise NotImplementedError
def fetch_metadata(self, project, renderer): def fetch_metadata(self, project, renderer):

View File

@@ -58,11 +58,11 @@ class GitPinnedPackage(GitPackageMixin, PinnedPackage):
self.subdirectory = subdirectory self.subdirectory = subdirectory
self._checkout_name = md5sum(self.name) self._checkout_name = md5sum(self.name)
def to_dict(self) -> Dict[str, str]: def to_dict(self) -> Dict[str, Optional[str]]:
git_scrubbed = scrub_secrets(self.git_unrendered, env_secrets()) git_scrubbed = scrub_secrets(self.git_unrendered, env_secrets())
if self.git_unrendered != git_scrubbed: if self.git_unrendered != git_scrubbed:
warn_or_error(DepsScrubbedPackageName(package_name=git_scrubbed)) warn_or_error(DepsScrubbedPackageName(package_name=git_scrubbed))
ret = { ret: Dict[str, Optional[str]] = {
"git": git_scrubbed, "git": git_scrubbed,
"revision": self.revision, "revision": self.revision,
} }

View File

@@ -1,5 +1,5 @@
import shutil import shutil
from typing import Dict from typing import Dict, Optional
from dbt.config.project import PartialProject, Project from dbt.config.project import PartialProject, Project
from dbt.config.renderer import PackageRenderer from dbt.config.renderer import PackageRenderer
@@ -11,9 +11,10 @@ from dbt_common.events.functions import fire_event
class LocalPackageMixin: class LocalPackageMixin:
def __init__(self, local: str) -> None: def __init__(self, local: str, project_root: Optional[str] = None) -> None:
super().__init__() super().__init__()
self.local = local self.local = local
self.project_root = project_root
@property @property
def name(self): def name(self):
@@ -24,12 +25,13 @@ class LocalPackageMixin:
class LocalPinnedPackage(LocalPackageMixin, PinnedPackage): class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
def __init__(self, local: str) -> None: def __init__(self, local: str, project_root: Optional[str] = None) -> None:
super().__init__(local) super().__init__(local, project_root)
def to_dict(self) -> Dict[str, str]: def to_dict(self) -> Dict[str, Optional[str]]:
return { return {
"local": self.local, "local": self.local,
"project_root": self.project_root,
} }
def get_version(self): def get_version(self):
@@ -38,10 +40,17 @@ class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
def nice_version_name(self): def nice_version_name(self):
return "<local @ {}>".format(self.local) return "<local @ {}>".format(self.local)
def resolve_path(self, project): def resolve_path(self, project: Project) -> str:
"""If `self.local` is a relative path, create an absolute path
with either `self.project_root` or `project.project_root` as the base.
If `self.local` is an absolute path or a user path (~), just
resolve it to an absolute path and return.
"""
return system.resolve_path_from_base( return system.resolve_path_from_base(
self.local, self.local,
project.project_root, self.project_root if self.project_root else project.project_root,
) )
def _fetch_metadata( def _fetch_metadata(
@@ -70,10 +79,10 @@ class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
class LocalUnpinnedPackage(LocalPackageMixin, UnpinnedPackage[LocalPinnedPackage]): class LocalUnpinnedPackage(LocalPackageMixin, UnpinnedPackage[LocalPinnedPackage]):
@classmethod @classmethod
def from_contract(cls, contract: LocalPackage) -> "LocalUnpinnedPackage": def from_contract(cls, contract: LocalPackage) -> "LocalUnpinnedPackage":
return cls(local=contract.local) return cls(local=contract.local, project_root=contract.project_root)
def incorporate(self, other: "LocalUnpinnedPackage") -> "LocalUnpinnedPackage": def incorporate(self, other: "LocalUnpinnedPackage") -> "LocalUnpinnedPackage":
return LocalUnpinnedPackage(local=self.local) return LocalUnpinnedPackage(local=other.local, project_root=other.project_root)
def resolved(self) -> LocalPinnedPackage: def resolved(self) -> LocalPinnedPackage:
return LocalPinnedPackage(local=self.local) return LocalPinnedPackage(local=self.local, project_root=self.project_root)

View File

@@ -1,4 +1,4 @@
from typing import Dict, List from typing import Dict, List, Optional
from dbt.clients import registry from dbt.clients import registry
from dbt.contracts.project import RegistryPackage, RegistryPackageMetadata from dbt.contracts.project import RegistryPackage, RegistryPackageMetadata
@@ -37,7 +37,7 @@ class RegistryPinnedPackage(RegistryPackageMixin, PinnedPackage):
def name(self): def name(self):
return self.package return self.package
def to_dict(self) -> Dict[str, str]: def to_dict(self) -> Dict[str, Optional[str]]:
return { return {
"package": self.package, "package": self.package,
"version": self.version, "version": self.version,

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Dict, Iterator, List, NoReturn, Set, Type from typing import Any, Dict, Iterator, List, NoReturn, Optional, Set, Type
from dbt.config import Project from dbt.config import Project
from dbt.config.renderer import PackageRenderer from dbt.config.renderer import PackageRenderer
@@ -66,11 +66,13 @@ class PackageListing:
else: else:
self.packages[key] = package self.packages[key] = package
def update_from(self, src: List[PackageSpec]) -> None: def update_from(self, src: List[PackageSpec], project_root: Optional[str] = None) -> None:
pkg: UnpinnedPackage pkg: UnpinnedPackage
for contract in src: for contract in src:
if isinstance(contract, LocalPackage): if isinstance(contract, LocalPackage):
pkg = LocalUnpinnedPackage.from_contract(contract) pkg = LocalUnpinnedPackage.from_contract(contract)
# Override the project root for the local package contract IFF it is provided
pkg.project_root = project_root if project_root else pkg.project_root
elif isinstance(contract, TarballPackage): elif isinstance(contract, TarballPackage):
pkg = TarballUnpinnedPackage.from_contract(contract) pkg = TarballUnpinnedPackage.from_contract(contract)
elif isinstance(contract, GitPackage): elif isinstance(contract, GitPackage):
@@ -86,9 +88,11 @@ class PackageListing:
self.incorporate(pkg) self.incorporate(pkg)
@classmethod @classmethod
def from_contracts(cls: Type["PackageListing"], src: List[PackageSpec]) -> "PackageListing": def from_contracts(
cls: Type["PackageListing"], src: List[PackageSpec], project_root: Optional[str] = None
) -> "PackageListing":
self = cls({}) self = cls({})
self.update_from(src) self.update_from(src, project_root=project_root)
return self return self
def resolved(self) -> List[PinnedPackage]: def resolved(self) -> List[PinnedPackage]:
@@ -118,7 +122,7 @@ def resolve_packages(
project: Project, project: Project,
cli_vars: Dict[str, Any], cli_vars: Dict[str, Any],
) -> List[PinnedPackage]: ) -> List[PinnedPackage]:
pending = PackageListing.from_contracts(packages) pending = PackageListing.from_contracts(packages, project_root=project.project_root)
final = PackageListing() final = PackageListing()
renderer = PackageRenderer(cli_vars) renderer = PackageRenderer(cli_vars)
@@ -129,7 +133,14 @@ def resolve_packages(
for package in pending: for package in pending:
final.incorporate(package) final.incorporate(package)
target = final[package].resolved().fetch_metadata(project, renderer) target = final[package].resolved().fetch_metadata(project, renderer)
next_pending.update_from(target.packages)
# Hack to get the project root if it is a LocalPackage
# https://github.com/dbt-labs/dbt-core/issues/5410
project_root = None
if isinstance(package, LocalUnpinnedPackage):
project_root = package.resolved().resolve_path(project)
next_pending.update_from(target.packages, project_root=project_root)
pending = next_pending pending = next_pending
resolved = final.resolved() resolved = final.resolved()

View File

@@ -1,7 +1,7 @@
import functools import functools
import os import os
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict, Optional
from dbt.config.project import PartialProject from dbt.config.project import PartialProject
from dbt.contracts.project import TarballPackage from dbt.contracts.project import TarballPackage
@@ -39,7 +39,7 @@ class TarballPinnedPackage(TarballPackageMixin, PinnedPackage):
def name(self): def name(self):
return self.package return self.package
def to_dict(self) -> Dict[str, str]: def to_dict(self) -> Dict[str, Optional[str]]:
tarball_scrubbed = scrub_secrets(self.tarball_unrendered, env_secrets()) tarball_scrubbed = scrub_secrets(self.tarball_unrendered, env_secrets())
if self.tarball_unrendered != tarball_scrubbed: if self.tarball_unrendered != tarball_scrubbed:
warn_or_error(DepsScrubbedPackageName(package_name=tarball_scrubbed)) warn_or_error(DepsScrubbedPackageName(package_name=tarball_scrubbed))