dbt-selly/dbt-env/lib/python3.8/site-packages/dbt/task/printer.py

383 lines
11 KiB
Python

from typing import Dict, Optional, Tuple, Callable
from dbt.logger import (
GLOBAL_LOGGER as logger,
DbtStatusMessage,
TextOnly,
get_timestamp,
)
from dbt.node_types import NodeType
from dbt.tracking import InvocationProcessor
from dbt import ui
from dbt import utils
from dbt.contracts.results import (
FreshnessStatus, NodeStatus, TestStatus
)
def print_fancy_output_line(
msg: str, status: str, logger_fn: Callable, index: Optional[int],
total: Optional[int], execution_time: Optional[float] = None,
truncate: bool = False
) -> None:
if index is None or total is None:
progress = ''
else:
progress = '{} of {} '.format(index, total)
prefix = "{timestamp} | {progress}{message}".format(
timestamp=get_timestamp(),
progress=progress,
message=msg)
truncate_width = ui.PRINTER_WIDTH - 3
justified = prefix.ljust(ui.PRINTER_WIDTH, ".")
if truncate and len(justified) > truncate_width:
justified = justified[:truncate_width] + '...'
if execution_time is None:
status_time = ""
else:
status_time = " in {execution_time:0.2f}s".format(
execution_time=execution_time)
output = "{justified} [{status}{status_time}]".format(
justified=justified, status=status, status_time=status_time)
logger_fn(output)
def get_counts(flat_nodes) -> str:
counts: Dict[str, int] = {}
for node in flat_nodes:
t = node.resource_type
if node.resource_type == NodeType.Model:
t = '{} {}'.format(node.get_materialization(), t)
elif node.resource_type == NodeType.Operation:
t = 'hook'
counts[t] = counts.get(t, 0) + 1
stat_line = ", ".join(
[utils.pluralize(v, k) for k, v in counts.items()])
return stat_line
def print_start_line(description: str, index: int, total: int) -> None:
msg = "START {}".format(description)
print_fancy_output_line(msg, 'RUN', logger.info, index, total)
def print_hook_start_line(statement: str, index: int, total: int) -> None:
msg = 'START hook: {}'.format(statement)
print_fancy_output_line(
msg, 'RUN', logger.info, index, total, truncate=True)
def print_hook_end_line(
statement: str, status: str, index: int, total: int, execution_time: float
) -> None:
msg = 'OK hook: {}'.format(statement)
# hooks don't fail into this path, so always green
print_fancy_output_line(msg, ui.green(status), logger.info, index, total,
execution_time=execution_time, truncate=True)
def print_skip_line(
node, schema: str, relation: str, index: int, num_models: int
) -> None:
if node.resource_type in NodeType.refable():
msg = f'SKIP relation {schema}.{relation}'
else:
msg = f'SKIP {node.resource_type} {node.name}'
print_fancy_output_line(
msg, ui.yellow('SKIP'), logger.info, index, num_models)
def print_cancel_line(model) -> None:
msg = 'CANCEL query {}'.format(model)
print_fancy_output_line(
msg, ui.red('CANCEL'), logger.error, index=None, total=None)
def get_printable_result(
result, success: str, error: str) -> Tuple[str, str, Callable]:
if result.status == NodeStatus.Error:
info = 'ERROR {}'.format(error)
status = ui.red(result.status.upper())
logger_fn = logger.error
else:
info = 'OK {}'.format(success)
status = ui.green(result.message)
logger_fn = logger.info
return info, status, logger_fn
def print_test_result_line(
result, index: int, total: int
) -> None:
model = result.node
if result.status == TestStatus.Error:
info = "ERROR"
color = ui.red
logger_fn = logger.error
elif result.status == TestStatus.Pass:
info = 'PASS'
color = ui.green
logger_fn = logger.info
elif result.status == TestStatus.Warn:
info = f'WARN {result.failures}'
color = ui.yellow
logger_fn = logger.warning
elif result.status == TestStatus.Fail:
info = f'FAIL {result.failures}'
color = ui.red
logger_fn = logger.error
else:
raise RuntimeError("unexpected status: {}".format(result.status))
print_fancy_output_line(
"{info} {name}".format(info=info, name=model.name),
color(info),
logger_fn,
index,
total,
result.execution_time)
def print_model_result_line(
result, description: str, index: int, total: int
) -> None:
info, status, logger_fn = get_printable_result(
result, 'created', 'creating')
print_fancy_output_line(
"{info} {description}".format(info=info, description=description),
status,
logger_fn,
index,
total,
result.execution_time)
def print_snapshot_result_line(
result, description: str, index: int, total: int
) -> None:
model = result.node
info, status, logger_fn = get_printable_result(
result, 'snapshotted', 'snapshotting')
cfg = model.config.to_dict(omit_none=True)
msg = "{info} {description}".format(
info=info, description=description, **cfg)
print_fancy_output_line(
msg,
status,
logger_fn,
index,
total,
result.execution_time)
def print_seed_result_line(result, schema_name: str, index: int, total: int):
model = result.node
info, status, logger_fn = get_printable_result(result, 'loaded', 'loading')
print_fancy_output_line(
"{info} seed file {schema}.{relation}".format(
info=info,
schema=schema_name,
relation=model.alias),
status,
logger_fn,
index,
total,
result.execution_time)
def print_freshness_result_line(result, index: int, total: int) -> None:
if result.status == FreshnessStatus.RuntimeErr:
info = 'ERROR'
color = ui.red
logger_fn = logger.error
elif result.status == FreshnessStatus.Error:
info = 'ERROR STALE'
color = ui.red
logger_fn = logger.error
elif result.status == FreshnessStatus.Warn:
info = 'WARN'
color = ui.yellow
logger_fn = logger.warning
else:
info = 'PASS'
color = ui.green
logger_fn = logger.info
if hasattr(result, 'node'):
source_name = result.node.source_name
table_name = result.node.name
else:
source_name = result.source_name
table_name = result.table_name
msg = f"{info} freshness of {source_name}.{table_name}"
print_fancy_output_line(
msg,
color(info),
logger_fn,
index,
total,
execution_time=result.execution_time
)
def interpret_run_result(result) -> str:
if result.status in (NodeStatus.Error, NodeStatus.Fail):
return 'error'
elif result.status == NodeStatus.Skipped:
return 'skip'
elif result.status == NodeStatus.Warn:
return 'warn'
elif result.status in (NodeStatus.Pass, NodeStatus.Success):
return 'pass'
else:
raise RuntimeError(f"unhandled result {result}")
def print_run_status_line(results) -> None:
stats = {
'error': 0,
'skip': 0,
'pass': 0,
'warn': 0,
'total': 0,
}
for r in results:
result_type = interpret_run_result(r)
stats[result_type] += 1
stats['total'] += 1
stats_line = "\nDone. PASS={pass} WARN={warn} ERROR={error} SKIP={skip} TOTAL={total}" # noqa
logger.info(stats_line.format(**stats))
def print_run_result_error(
result, newline: bool = True, is_warning: bool = False
) -> None:
if newline:
with TextOnly():
logger.info("")
if result.status == NodeStatus.Fail or (
is_warning and result.status == NodeStatus.Warn
):
if is_warning:
color = ui.yellow
info = 'Warning'
logger_fn = logger.warning
else:
color = ui.red
info = 'Failure'
logger_fn = logger.error
logger_fn(color("{} in {} {} ({})").format(
info,
result.node.resource_type,
result.node.name,
result.node.original_file_path))
if result.message:
logger.error(f" {result.message}")
else:
logger.error(f" Status: {result.status}")
if result.node.build_path is not None:
with TextOnly():
logger.info("")
logger.info(" compiled SQL at {}".format(
result.node.compiled_path))
if result.node.should_store_failures:
with TextOnly():
logger.info("")
msg = f"select * from {result.node.relation_name}"
border = '-' * len(msg)
logger.info(f" See test failures:\n {border}\n {msg}\n {border}")
elif result.message is not None:
first = True
for line in result.message.split("\n"):
if first:
logger.error(ui.yellow(line))
first = False
else:
logger.error(line)
def print_skip_caused_by_error(
model, schema: str, relation: str, index: int, num_models: int, result
) -> None:
msg = ('SKIP relation {}.{} due to ephemeral model error'
.format(schema, relation))
print_fancy_output_line(
msg, ui.red('ERROR SKIP'), logger.error, index, num_models)
print_run_result_error(result, newline=False)
def print_end_of_run_summary(
num_errors: int, num_warnings: int, keyboard_interrupt: bool = False
) -> None:
error_plural = utils.pluralize(num_errors, 'error')
warn_plural = utils.pluralize(num_warnings, 'warning')
if keyboard_interrupt:
message = ui.yellow('Exited because of keyboard interrupt.')
elif num_errors > 0:
message = ui.red("Completed with {} and {}:".format(
error_plural, warn_plural))
elif num_warnings > 0:
message = ui.yellow('Completed with {}:'.format(warn_plural))
else:
message = ui.green('Completed successfully')
with TextOnly():
logger.info('')
logger.info('{}'.format(message))
def print_run_end_messages(results, keyboard_interrupt: bool = False) -> None:
errors, warnings = [], []
for r in results:
if r.status in (
NodeStatus.RuntimeErr,
NodeStatus.Error,
NodeStatus.Fail
):
errors.append(r)
elif r.status == NodeStatus.Skipped and r.message is not None:
# this means we skipped a node because of an issue upstream,
# so include it as an error
errors.append(r)
elif r.status == NodeStatus.Warn:
warnings.append(r)
with DbtStatusMessage(), InvocationProcessor():
print_end_of_run_summary(len(errors),
len(warnings),
keyboard_interrupt)
for error in errors:
print_run_result_error(error, is_warning=False)
for warning in warnings:
print_run_result_error(warning, is_warning=True)
print_run_status_line(results)