Compare commits

...

2 Commits

Author SHA1 Message Date
Emily Rockman
ad9aabca1d fix merge conflict leftovers 2022-11-29 09:25:42 -06:00
Emily Rockman
ea1304f8bb WIP
WIP

WIP

WIP

WIP

added data to exception

log all class props

WIP
2022-11-29 08:17:39 -06:00
22 changed files with 434 additions and 129 deletions

View File

@@ -14,7 +14,7 @@ from dbt.exceptions import DbtProfileError
from dbt.exceptions import DbtProjectError
from dbt.exceptions import ValidationException
from dbt.exceptions import RuntimeException
from dbt.exceptions import validator_error_message
from dbt.exception_messages import validator_error_message
from dbt.events.types import MissingProfileTarget
from dbt.events.functions import fire_event
from dbt.utils import coerce_dict_str

View File

@@ -21,9 +21,9 @@ from dbt.clients.system import path_exists
from dbt.clients.system import load_file_contents
from dbt.clients.yaml_helper import load_yaml_text
from dbt.contracts.connection import QueryComment
from dbt.exception_messages import validator_error_message
from dbt.exceptions import DbtProjectError
from dbt.exceptions import SemverException
from dbt.exceptions import validator_error_message
from dbt.exceptions import RuntimeException
from dbt.graph import SelectionSpec
from dbt.helper_types import NoValue

View File

@@ -24,11 +24,11 @@ from dbt.contracts.graph.manifest import ManifestMetadata
from dbt.contracts.project import Configuration, UserConfig
from dbt.contracts.relation import ComponentName
from dbt.dataclass_schema import ValidationError
from dbt.exception_messages import validator_error_message
from dbt.exceptions import (
DbtProjectError,
RuntimeException,
raise_compiler_error,
validator_error_message,
)
from dbt.events.functions import warn_or_error
from dbt.events.types import UnusedResourceConfigPath

View File

@@ -31,7 +31,7 @@ class DBTDeprecation:
except AttributeError:
msg = f"Event Class `{class_name}` is not defined in `{module_path}`"
raise NameError(msg)
raise NotImplementedError("event not implemented for {}".format(self._event))
raise NotImplementedError("event not implemented for {}".format(self))
def show(self, *args, **kwargs) -> None:
if self.name not in active_deprecations:

237
core/dbt/deps/exceptions.py Normal file
View File

@@ -0,0 +1,237 @@
import abc
import builtins
from typing import ClassVar, NoReturn, Dict
import dbt.events
from dbt.events.helpers import env_secrets, scrub_secrets
# TODO: using this special case here to not break the rest of exceptions but this would normally
# live centrally
class Exception(builtins.Exception):
CODE = -32000
MESSAGE = "Server Error"
_event: ClassVar[str] = "GeneralException"
_category: str = "general exception"
def __init__(self):
super().__init__()
self.log()
def data(self) -> Dict[str, str]:
# do not override
constant_exception_data = {
"type": self.__class__.__name__, # is this used outside logbook logs?
"message": str(self),
"event": self._event, # does not always match type
"category": self._category,
}
# TODO: can't guarantee this is always serializable...
return {**constant_exception_data, **vars(self)}
@property
def event(self) -> abc.ABCMeta:
if self._event is not None:
module_path = dbt.events.types
class_name = self._event
try:
return getattr(module_path, class_name)
except AttributeError:
msg = f"Event Class `{class_name}` is not defined in `{module_path}`"
raise NameError(msg)
raise NotImplementedError("event not implemented for {}".format(self))
def log(self, *args, **kwargs) -> None:
log_event = self.event(data=self.data(), **kwargs)
dbt.events.functions.fire_event(log_event)
class RuntimeException(RuntimeError, Exception):
CODE = 10001
MESSAGE = "Runtime error"
def __init__(self, msg, node=None):
self.stack = []
self.node = node
self.message = scrub_secrets(msg, env_secrets())
def add_node(self, node=None):
if node is not None and node is not self.node:
if self.node is not None:
self.stack.append(self.node)
self.node = node
@property
def type(self):
return "Runtime"
def node_to_string(self, node):
if node is None:
return "<Unknown>"
if not hasattr(node, "name"):
# we probably failed to parse a block, so we can't know the name
return "{} ({})".format(node.resource_type, node.original_file_path)
if hasattr(node, "contents"):
# handle FileBlocks. They aren't really nodes but we want to render
# out the path we know at least. This indicates an error during
# block parsing.
return "{}".format(node.path.original_file_path)
return "{} {} ({})".format(node.resource_type, node.name, node.original_file_path)
def process_stack(self):
lines = []
stack = self.stack + [self.node]
first = True
if len(stack) > 1:
lines.append("")
for item in stack:
msg = "called by"
if first:
msg = "in"
first = False
lines.append("> {} {}".format(msg, self.node_to_string(item)))
return lines
def __str__(self, prefix="! "):
node_string = ""
if self.node is not None:
node_string = " in {}".format(self.node_to_string(self.node))
if hasattr(self.message, "split"):
split_msg = self.message.split("\n")
else:
split_msg = str(self.message).split("\n")
lines = ["{}{}".format(self.type + " Error", node_string)] + split_msg
lines += self.process_stack()
return lines[0] + "\n" + "\n".join([" " + line for line in lines[1:]])
def data(self):
result = Exception.data(self)
if self.node is None:
return result
result.update(
{
"raw_code": self.node.raw_code,
# the node isn't always compiled, but if it is, include that!
"compiled_code": getattr(self.node, "compiled_code", None),
}
)
return result
# TODO: caused some circular imports. copied here. doesn't belong here.
class CommandError(RuntimeException):
def __init__(self, cwd, cmd, message="Error running command"):
cmd_scrubbed = list(scrub_secrets(cmd_txt, env_secrets()) for cmd_txt in cmd)
super().__init__(message)
self.cwd = cwd
self.cmd = cmd_scrubbed
self.args = (cwd, cmd_scrubbed, message)
def __str__(self):
if len(self.cmd) == 0:
return "{}: No arguments given".format(self.message)
return '{}: "{}"'.format(self.message, self.cmd[0])
# Start actual deps exceptions
class DependencyException(Exception):
# this can happen due to raise_dependency_error and its callers
CODE = 10006
MESSAGE = "Dependency Error"
_event: ClassVar[str] = "DependencyException"
_category: str = "general deps"
def __init__(self, message):
super().__init__()
self.message = scrub_secrets(message, env_secrets())
# TODO: explore where this should live
class ExecutableError(CommandError):
def __init__(self, cwd, cmd, message):
super().__init__(cwd, cmd, message)
class InternalException(DependencyException):
_category: str = "internal"
# This was using SemverException previously...
class DependencyVersionException(DependencyException):
_category: str = "version"
def __init__(self, name):
self.name = name
msg = "Version error for package {}: {}".format(self.name, self)
super().__init__(msg)
class MultipleDependencyVersionException(DependencyException):
_category: str = "git"
def __init__(self, git, requested):
self.git = git
self.requested = requested
msg = "git dependencies should contain exactly one version. " "{} contains: {}".format(
self.git, requested
)
super().__init__(msg)
class PackageNotFound(DependencyException):
def __init__(self, package_name):
self.package_name = package_name
msg = f"Package {self.package_name} was not found in the package index"
super().__init__(msg)
class PackageVersionNotFound(DependencyException):
_category: str = "config"
def __init__(self, package_name, version_range, available_versions, should_version_check):
self.package_name = package_name
self.version_range = str(version_range)
self.available_versions = available_versions
self.should_version_check = should_version_check
msg = self.build_msg()
super().__init__(msg)
def build_msg(self):
base_msg = (
"Could not find a matching compatible version for package {}\n"
" Requested range: {}\n"
" Compatible versions: {}\n"
)
addendum = (
(
"\n"
" Not shown: package versions incompatible with installed version of dbt-core\n"
" To include them, run 'dbt --no-version-check deps'"
)
if self.should_version_check
else ""
)
return (
base_msg.format(self.package_name, self.version_range, self.available_versions)
+ addendum
)
# should these all become their own exceptions? They have to all share a category if not.
def raise_dependency_error(msg) -> NoReturn:
raise DependencyException(scrub_secrets(msg, env_secrets()))

View File

@@ -9,7 +9,7 @@ from dbt.contracts.project import (
GitPackage,
)
from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
from dbt.exceptions import ExecutableError, raise_dependency_error
from .exceptions import ExecutableError, MultipleDependencyVersionException
from dbt.events.functions import fire_event, warn_or_error
from dbt.events.types import EnsureGitInstalled, DepsUnpinned
@@ -143,10 +143,7 @@ class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
if len(requested) == 0:
requested = {"HEAD"}
elif len(requested) > 1:
raise_dependency_error(
"git dependencies should contain exactly one version. "
"{} contains: {}".format(self.git, requested)
)
raise MultipleDependencyVersionException(git=self.git, requested=requested)
return GitPinnedPackage(
git=self.git,

View File

@@ -11,11 +11,13 @@ from dbt.contracts.project import (
RegistryPackage,
)
from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
from dbt.exceptions import (
package_version_not_found,
VersionsNotCompatibleException,
DependencyException,
package_not_found,
from dbt.deps.exceptions import (
DependencyVersionException,
)
from dbt.exceptions import VersionsNotCompatibleException
from .exceptions import (
PackageVersionNotFound,
PackageNotFound,
)
from dbt.utils import _connection_exception_retry as connection_exception_retry
@@ -99,7 +101,7 @@ class RegistryUnpinnedPackage(RegistryPackageMixin, UnpinnedPackage[RegistryPinn
def _check_in_index(self):
index = registry.index_cached()
if self.package not in index:
package_not_found(self.package)
raise PackageNotFound(self.package)
@classmethod
def from_contract(cls, contract: RegistryPackage) -> "RegistryUnpinnedPackage":
@@ -124,8 +126,7 @@ class RegistryUnpinnedPackage(RegistryPackageMixin, UnpinnedPackage[RegistryPinn
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
raise DependencyVersionException(self.name) from e
should_version_check = bool(flags.VERSION_CHECK)
dbt_version = get_installed_version()
@@ -146,7 +147,12 @@ class RegistryUnpinnedPackage(RegistryPackageMixin, UnpinnedPackage[RegistryPinn
target = None
if not target:
# raise an exception if no installable target version is found
package_version_not_found(self.package, range_, installable, should_version_check)
raise PackageVersionNotFound(
package_name=self.package,
version_range=range_,
available_versions=installable,
should_version_check=should_version_check,
)
latest_compatible = installable[-1]
return RegistryPinnedPackage(
package=self.package, version=target, version_latest=latest_compatible

View File

@@ -1,7 +1,7 @@
from dataclasses import dataclass, field
from typing import Dict, List, NoReturn, Union, Type, Iterator, Set
from dbt.exceptions import raise_dependency_error, InternalException
from .exceptions import raise_dependency_error, InternalException
from dbt.config import Project, RuntimeConfig
from dbt.config.renderer import DbtProjectYamlRenderer

View File

@@ -1495,6 +1495,16 @@ class NoNodesForSelectionCriteria(betterproto.Message):
spec_raw: str = betterproto.string_field(2)
@dataclass
class DependencyException(betterproto.Message):
"""M031"""
info: "EventInfo" = betterproto.message_field(1)
data: Dict[str, str] = betterproto.map_field(
3, betterproto.TYPE_STRING, betterproto.TYPE_STRING
)
@dataclass
class RunningOperationCaughtError(betterproto.Message):
"""Q001"""
@@ -1827,6 +1837,13 @@ class FoundStats(betterproto.Message):
stat_line: str = betterproto.string_field(2)
@dataclass
class GeneralException(betterproto.Message):
"""X001"""
info: "EventInfo" = betterproto.message_field(1)
@dataclass
class MainKeyboardInterrupt(betterproto.Message):
"""Z001"""

View File

@@ -1136,6 +1136,12 @@ message NoNodesForSelectionCriteria {
string spec_raw = 2;
}
// M031
message DependencyException {
EventInfo info = 1;
map<string, string> data = 3;
}
// Q - Node execution
// Q001
@@ -1416,6 +1422,14 @@ message FoundStats {
string stat_line = 2;
}
// X - Exceptions
//X001
message GeneralException {
EventInfo info = 1;
}
// Z - Misc
// Z001

View File

@@ -18,6 +18,7 @@ from dbt.events.format import format_fancy_output_line, pluralize
from dbt.events.proto_types import EventInfo, RunResultMsg, ListOfStrings # noqa
from dbt.events.proto_types import NodeInfo, ReferenceKeyMsg # noqa
from dbt.events import proto_types as pt
from dbt.exception_messages import get_not_found_or_disabled_msg_2
from dbt.node_types import NodeType
@@ -40,6 +41,7 @@ from dbt.node_types import NodeType
# | M | Deps generation |
# | Q | Node execution |
# | W | Node testing |
# | X | Exceptions |
# | Z | Misc |
# | T | Test only |
#
@@ -1491,28 +1493,14 @@ class NodeNotFoundOrDisabled(WarnLevel, pt.NodeNotFoundOrDisabled):
return "I060"
def message(self) -> str:
# this is duplicated logic from exceptions.get_not_found_or_disabled_msg
# when we convert exceptions to be stuctured maybe it can be combined?
# convverting the bool to a string since None is also valid
if self.disabled == "None":
reason = "was not found or is disabled"
elif self.disabled == "True":
reason = "is disabled"
else:
reason = "was not found"
target_package_string = ""
if self.target_package is not None:
target_package_string = "in package '{}' ".format(self.target_package)
msg = "{} '{}' ({}) depends on a {} named '{}' {}which {}".format(
self.resource_type_title,
self.unique_id,
self.original_file_path,
self.target_kind,
self.target_name,
target_package_string,
reason,
msg = get_not_found_or_disabled_msg_2(
original_file_path=self.original_file_path,
unique_id=self.unique_id,
resource_type_title=self.resource_type_title,
target_name=self.target_name,
target_kind=self.target_kind,
target_package=self.target_package,
disabled=self.disabled,
)
return warning_tag(msg)
@@ -1824,6 +1812,15 @@ class NoNodesForSelectionCriteria(WarnLevel, pt.NoNodesForSelectionCriteria):
return f"The selection criterion '{self.spec_raw}' does not match any nodes"
@dataclass
class DependencyException(ErrorLevel, pt.DependencyException):
def code(self):
return "M031"
def message(self) -> str:
return f"This is the custom message: {self.data.get('message')}"
# =======================================================
# Q - Node execution
# =======================================================
@@ -2298,6 +2295,20 @@ class FoundStats(InfoLevel, pt.FoundStats):
return f"Found {self.stat_line}"
# =======================================================
# X - Exceptions
# =======================================================
@dataclass
class GeneralException(ErrorLevel, pt.GeneralException):
def code(self):
return "X001"
def message(self) -> str:
return "General Exception"
# =======================================================
# Z - Misc
# =======================================================

View File

@@ -0,0 +1,75 @@
import dbt.dataclass_schema
from typing import Optional
def validator_error_message(exc):
"""Given a dbt.dataclass_schema.ValidationError (which is basically a
jsonschema.ValidationError), return the relevant parts as a string
"""
if not isinstance(exc, dbt.dataclass_schema.ValidationError):
return str(exc)
path = "[%s]" % "][".join(map(repr, exc.relative_path))
return "at path {}: {}".format(path, exc.message)
def get_not_found_or_disabled_msg(
original_file_path,
unique_id,
resource_type_title,
target_name: str,
target_kind: str,
target_package: Optional[str] = None,
disabled: Optional[bool] = None,
) -> str:
if disabled is None:
reason = "was not found or is disabled"
elif disabled is True:
reason = "is disabled"
else:
reason = "was not found"
target_package_string = ""
if target_package is not None:
target_package_string = "in package '{}' ".format(target_package)
return "{} '{}' ({}) depends on a {} named '{}' {}which {}".format(
resource_type_title,
unique_id,
original_file_path,
target_kind,
target_name,
target_package_string,
reason,
)
# TODO: temp fix for mypy/proto sanity - combine later
def get_not_found_or_disabled_msg_2(
original_file_path,
unique_id,
resource_type_title,
target_name: str,
target_kind: str,
disabled: str,
target_package: Optional[str] = None,
) -> str:
if disabled == "None":
reason = "was not found or is disabled"
elif disabled == "True":
reason = "is disabled"
else:
reason = "was not found"
target_package_string = ""
if target_package is not None:
target_package_string = "in package '{}' ".format(target_package)
return "{} '{}' ({}) depends on a {} named '{}' {}which {}".format(
resource_type_title,
unique_id,
original_file_path,
target_kind,
target_name,
target_package_string,
reason,
)

View File

@@ -1,24 +1,15 @@
import builtins
import functools
from typing import NoReturn, Optional, Mapping, Any
from typing import Any, Mapping, NoReturn, Optional
from dbt.events.helpers import env_secrets, scrub_secrets
from dbt.events.types import JinjaLogWarning
from dbt.exception_messages import get_not_found_or_disabled_msg
from dbt.node_types import NodeType
import dbt.dataclass_schema
def validator_error_message(exc):
"""Given a dbt.dataclass_schema.ValidationError (which is basically a
jsonschema.ValidationError), return the relevant parts as a string
"""
if not isinstance(exc, dbt.dataclass_schema.ValidationError):
return str(exc)
path = "[%s]" % "][".join(map(repr, exc.relative_path))
return "at path {}: {}".format(path, exc.message)
class Exception(builtins.Exception):
CODE = -32000
MESSAGE = "Server Error"
@@ -309,12 +300,6 @@ class AliasException(ValidationException):
pass
class DependencyException(Exception):
# this can happen due to raise_dependency_error and its callers
CODE = 10006
MESSAGE = "Dependency Error"
class DbtConfigError(RuntimeException):
CODE = 10007
MESSAGE = "DBT Configuration Error"
@@ -451,10 +436,6 @@ def raise_database_error(msg, node=None) -> NoReturn:
raise DatabaseException(msg, node)
def raise_dependency_error(msg) -> NoReturn:
raise DependencyException(scrub_secrets(msg, env_secrets()))
def raise_git_cloning_error(error: CommandResultError) -> NoReturn:
error.cmd = scrub_secrets(str(error.cmd), env_secrets())
raise error
@@ -568,37 +549,6 @@ def doc_target_not_found(
raise_compiler_error(msg, model)
def get_not_found_or_disabled_msg(
original_file_path,
unique_id,
resource_type_title,
target_name: str,
target_kind: str,
target_package: Optional[str] = None,
disabled: Optional[bool] = None,
) -> str:
if disabled is None:
reason = "was not found or is disabled"
elif disabled is True:
reason = "is disabled"
else:
reason = "was not found"
target_package_string = ""
if target_package is not None:
target_package_string = "in package '{}' ".format(target_package)
return "{} '{}' ({}) depends on a {} named '{}' {}which {}".format(
resource_type_title,
unique_id,
original_file_path,
target_kind,
target_name,
target_package_string,
reason,
)
def target_not_found(
node,
target_name: str,
@@ -718,31 +668,6 @@ def relation_wrong_type(relation, expected_type, model=None):
)
def package_not_found(package_name):
raise_dependency_error("Package {} was not found in the package index".format(package_name))
def package_version_not_found(
package_name, version_range, available_versions, should_version_check
):
base_msg = (
"Could not find a matching compatible version for package {}\n"
" Requested range: {}\n"
" Compatible versions: {}\n"
)
addendum = (
(
"\n"
" Not shown: package versions incompatible with installed version of dbt-core\n"
" To include them, run 'dbt --no-version-check deps'"
)
if should_version_check
else ""
)
msg = base_msg.format(package_name, version_range, available_versions) + addendum
raise_dependency_error(msg)
def invalid_materialization_argument(name, argument):
raise_compiler_error(
"materialization '{}' received unknown argument '{}'.".format(name, argument)
@@ -1000,6 +925,9 @@ def warn(msg, node=None):
return ""
# TODO: importing here just to track things separately
from dbt.deps.exceptions import raise_dependency_error # noqa
# Update this when a new function should be added to the
# dbt context's `exceptions` key!
CONTEXT_EXPORTS = {

View File

@@ -18,7 +18,8 @@ from dbt.context.context_config import ContextConfig
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.parsed import HasUniqueID, ManifestNodes
from dbt.contracts.graph.unparsed import UnparsedNode, Docs
from dbt.exceptions import ParsingException, validator_error_message, InternalException
from dbt.exception_messages import validator_error_message
from dbt.exceptions import ParsingException, InternalException
from dbt import hooks
from dbt.node_types import NodeType, ModelLanguage
from dbt.parser.search import FileBlock

View File

@@ -978,7 +978,7 @@ def invalid_target_fail_unless_test(
target_name=target_name,
target_kind=target_kind,
target_package=target_package if target_package else "",
disabled=str(disabled),
disabled="None" if disabled is None else str(disabled),
)
)
else:

View File

@@ -29,7 +29,8 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
# New for Python models :p
import ast
from dbt.dataclass_schema import ValidationError
from dbt.exceptions import ParsingException, validator_error_message, UndefinedMacroException
from dbt.exception_messages import validator_error_message
from dbt.exceptions import ParsingException, UndefinedMacroException
dbt_function_key_words = set(["ref", "source", "config", "get"])

View File

@@ -49,8 +49,8 @@ from dbt.contracts.graph.unparsed import (
UnparsedMetric,
UnparsedSourceDefinition,
)
from dbt.exception_messages import validator_error_message
from dbt.exceptions import (
validator_error_message,
JSONValidationException,
raise_invalid_property_yml_version,
ValidationException,

View File

@@ -4,7 +4,8 @@ from typing import List
from dbt.dataclass_schema import ValidationError
from dbt.contracts.graph.parsed import IntermediateSnapshotNode, ParsedSnapshotNode
from dbt.exceptions import ParsingException, validator_error_message
from dbt.exception_messages import validator_error_message
from dbt.exceptions import ParsingException
from dbt.node_types import NodeType
from dbt.parser.base import SQLParser
from dbt.parser.search import BlockContents, BlockSearcher, FileBlock

View File

@@ -174,6 +174,7 @@ class ModelRunner(CompileRunner):
return f"{self.node.language} {self.node.get_materialization()} model {self.get_node_representation()}"
def print_start_line(self):
breakpoint()
fire_event(
LogStartLine(
description=self.describe_node(),

View File

@@ -4,7 +4,7 @@ import unittest
from unittest import mock
import dbt.deps
import dbt.exceptions
import dbt.deps.exceptions
from dbt.deps.git import GitUnpinnedPackage
from dbt.deps.local import LocalUnpinnedPackage
from dbt.deps.registry import RegistryUnpinnedPackage
@@ -92,7 +92,7 @@ class TestGitPackage(unittest.TestCase):
self.assertEqual(c.git, 'http://example.com')
self.assertEqual(c.revisions, ['0.0.1', '0.0.2'])
with self.assertRaises(dbt.exceptions.DependencyException):
with self.assertRaises(dbt.deps.exceptions.DependencyException):
c.resolved()
def test_default_revision(self):
@@ -223,7 +223,7 @@ class TestHubPackage(unittest.TestCase):
package='dbt-labs-test/b',
version='0.1.2'
))
with self.assertRaises(dbt.exceptions.DependencyException) as exc:
with self.assertRaises(dbt.deps.exceptions.PackageNotFound) as exc:
a.resolved()
msg = 'Package dbt-labs-test/b was not found in the package index'
@@ -235,7 +235,7 @@ class TestHubPackage(unittest.TestCase):
version='0.1.4'
))
with self.assertRaises(dbt.exceptions.DependencyException) as exc:
with self.assertRaises(dbt.deps.exceptions.PackageVersionNotFound) as exc:
a.resolved()
msg = (
"Could not find a matching compatible version for package "
@@ -257,7 +257,7 @@ class TestHubPackage(unittest.TestCase):
b = RegistryUnpinnedPackage.from_contract(b_contract)
c = a.incorporate(b)
with self.assertRaises(dbt.exceptions.DependencyException) as exc:
with self.assertRaises(dbt.deps.exceptions.DependencyVersionException) as exc:
c.resolved()
msg = (
"Version error for package dbt-labs-test/a: Could not "

View File

@@ -80,8 +80,11 @@ class BaseAliasErrors:
def test_alias_dupe_thorews_exeption(self, project):
message = ".*identical database representation.*"
with pytest.raises(Exception) as exc:
assert message in exc
# assert message in exc
run_dbt(["run"])
assert message in exc
# breakpoint()
# print("ab")
class BaseSameAliasDifferentSchemas:

View File

@@ -0,0 +1,13 @@
# flake8: noqa
import pytest
from dbt.exceptions import Exception
class TestBaseException:
def test_base_exception(self):
with pytest.raises(Exception) as exc_info:
raise(Exception())
breakpoint()
print("hi")