import os from typing import List from dbt import semver from dbt.clients import registry, system from dbt.contracts.project import ( RegistryPackageMetadata, RegistryPackage, ) from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path from dbt.exceptions import ( package_version_not_found, VersionsNotCompatibleException, DependencyException, package_not_found, ) class RegistryPackageMixin: def __init__(self, package: str) -> None: super().__init__() self.package = package @property def name(self): return self.package def source_type(self) -> str: return 'hub' class RegistryPinnedPackage(RegistryPackageMixin, PinnedPackage): def __init__(self, package: str, version: str, version_latest: str) -> None: super().__init__(package) self.version = version self.version_latest = version_latest @property def name(self): return self.package def source_type(self): return 'hub' def get_version(self): return self.version def get_version_latest(self): return self.version_latest def nice_version_name(self): return 'version {}'.format(self.version) def _fetch_metadata(self, project, renderer) -> RegistryPackageMetadata: dct = registry.package_version(self.package, self.version) return RegistryPackageMetadata.from_dict(dct) def install(self, project, renderer): metadata = self.fetch_metadata(project, renderer) tar_name = '{}.{}.tar.gz'.format(self.package, self.version) tar_path = os.path.realpath( os.path.join(get_downloads_path(), tar_name) ) system.make_directory(os.path.dirname(tar_path)) download_url = metadata.downloads.tarball system.download_with_retries(download_url, tar_path) deps_path = project.modules_path package_name = self.get_project_name(project, renderer) system.untar_package(tar_path, deps_path, package_name) class RegistryUnpinnedPackage( RegistryPackageMixin, UnpinnedPackage[RegistryPinnedPackage] ): def __init__( self, package: str, versions: List[semver.VersionSpecifier], install_prerelease: bool ) -> None: super().__init__(package) self.versions = versions self.install_prerelease = install_prerelease def _check_in_index(self): index = registry.index_cached() if self.package not in index: package_not_found(self.package) @classmethod def from_contract( cls, contract: RegistryPackage ) -> 'RegistryUnpinnedPackage': raw_version = contract.get_versions() versions = [ semver.VersionSpecifier.from_version_string(v) for v in raw_version ] return cls( package=contract.package, versions=versions, install_prerelease=contract.install_prerelease ) def incorporate( self, other: 'RegistryUnpinnedPackage' ) -> 'RegistryUnpinnedPackage': return RegistryUnpinnedPackage( package=self.package, install_prerelease=self.install_prerelease, versions=self.versions + other.versions, ) def resolved(self) -> RegistryPinnedPackage: self._check_in_index() try: range_ = semver.reduce_versions(*self.versions) except VersionsNotCompatibleException as e: new_msg = ('Version error for package {}: {}' .format(self.name, e)) raise DependencyException(new_msg) from e available = registry.get_available_versions(self.package) installable = semver.filter_installable( available, self.install_prerelease ) available_latest = installable[-1] # for now, pick a version and then recurse. later on, # we'll probably want to traverse multiple options # so we can match packages. not going to make a difference # right now. target = semver.resolve_to_specific_version(range_, installable) if not target: package_version_not_found(self.package, range_, installable) return RegistryPinnedPackage(package=self.package, version=target, version_latest=available_latest)