Как изменить цвет имени файла и номера строки в выводе pytest?Python

Программы на Python
Ответить
Anonymous
 Как изменить цвет имени файла и номера строки в выводе pytest?

Сообщение Anonymous »


Изображение

У меня есть приведенный выше вывод из pytest чтобы было легче найти интересующую информацию. Единственное, что я хотел бы сделать, это сделать имя файла и номер строки выделенными другим цветом.
У меня есть специальный модуль log_helper, который я использую:

Код: Выделить всё

import logging
import os
import pprint

# Initialize the logger at the module level, so it's only created once
logger = logging.getLogger('debug_logger')

# Set up the logger only if it hasn't been set up already
if not logger.handlers:
log_level = os.environ.get('LOG_LEVEL', 'WARNING').upper()
logger.setLevel(getattr(logging, log_level, logging.WARNING))

formatter = logging.Formatter('%(levelname)s: %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

class RelativePathFormatter(logging.Formatter):
def __init__(self, *args, base_path=None, **kwargs):
super().__init__(*args, **kwargs)
# Define the base path (in your case ~/python)
self.base_path = base_path if base_path else os.path.expanduser('~/python')

def format(self, record):
# Modify the pathname to be relative to the base path
if record.pathname.startswith(self.base_path):
record.pathname = os.path.relpath(record.pathname, self.base_path)
return super().format(record)

# Setup logger using the custom formatter
def get_logger(name='debug_logger'):
logger = logging.getLogger(name)

# Remove existing handlers to avoid conflicts
if logger.hasHandlers():
logger.handlers.clear()

log_level = os.environ.get('LOG_LEVEL', 'DEBUG').upper()
logger.setLevel(getattr(logging, log_level, logging.DEBUG))

# Apply custom formatter
formatter = RelativePathFormatter(
'%(levelname)-8s %(pathname)s:%(lineno)d - %(message)s',
base_path=os.path.expanduser('~/python')
)

handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

return logger

# Debug print function that respects log level and logs caller info
def d(data, depth=None):
"""
Debug print function that logs data in DEBUG mode with optional depth control for complex data structures.
Shows the correct file and line number of the caller, not log_helper.py.

:param data: The data to log (complex or simple).
:param depth: Optional depth to limit the structure's representation, defaults to None.
"""
logger = get_logger()  # Get the global logger

# Bail out immediately if the logger is not in DEBUG mode
if not logger.isEnabledFor(logging.DEBUG):
return

# Format the data for debug output
formatted_data = pprint.pformat(data, depth=depth)

# Check if the formatted data has multiple lines
if "\n" in formatted_data:
# Add a newline before the data if it spans multiple lines
formatted_data = "\n"  + formatted_data

# Adjust the stack level so the logger reports the caller's file and line number
logger.debug(formatted_data, stacklevel=2)
Я также использую файл pytest.ini:

Код: Выделить всё

[pytest]
# Include numbered files as test files
python_files = test_*.py *_test.py [0-9]*-*.py 00-*_test
log_cli_format = %(levelname)-8s %(pathname)s:%(lineno)d - %(message)s
Наконец, у меня есть файл conftest.py:

Код: Выделить всё

# File: ~/python/tests/conftest.py

import time
from pathlib import Path
from collections import defaultdict

import pytest

# ANSI color codes
CYAN = '\033[96m'
YELLOW = '\033[93m'
GREEN = '\033[92m'
RED = '\033[91m'
RESET = '\033[0m'

test_results = {}
test_structure = defaultdict(lambda: defaultdict(list))
start_time = None

import pytest  # Make sure pytest is imported
import logging

@pytest.hookimpl(trylast=True)
def pytest_configure(config):
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")

# Change color on existing log level
logging_plugin.log_cli_handler.formatter.add_color_level(logging.DEBUG, "blue")
logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, "cyan")

def pytest_sessionstart(session):
global start_time
start_time = time.time()

def pytest_collection_modifyitems(session, config, items):
# for item in items:
#     path = Path(item.fspath).relative_to(Path(session.config.rootdir))
#     dir_path = path.parent
#     file_name = path.name
#     test_structure[dir_path][file_name].append(item)

# Sort items based on their location in the file
items.sort(key=lambda x: x.location[1])

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
result = outcome.get_result()

if result.when == 'call':
test_results[item.nodeid] = result.outcome

def pytest_sessionfinish(session, exitstatus):
global start_time
duration = time.time() - start_time

verbosity = session.config.option.verbose

if verbosity > 1:
print("\n")
for dir_path in sorted(test_structure.keys()):
print(f"\n{CYAN}{dir_path}{RESET}")
for file_name in sorted(test_structure[dir_path].keys()):
print(f"  {YELLOW}{file_name}{RESET}")
for item in test_structure[dir_path][file_name]:
result = test_results.get(item.nodeid, "UNKNOWN")
if result == "passed":
color = GREEN
elif result == "failed":
color = RED
else:
color = YELLOW
indent = "    "
if "::" in item.name:
class_name, test_name = item.name.split("::")
print(f"    {class_name}")
indent = "      "
else:
test_name = item.name
print(f"{indent}{test_name} {color}{result.upper()}{RESET}")

# Print summary
passed = sum(1 for result in test_results.values() if result == "passed")
failed = sum(1 for result in test_results.values() if result == "failed")
total = len(test_results)
print(f"\n{GREEN if failed == 0 else RED}=== {passed} passed, {failed} failed, {total} total in {duration:.2f}s ==={RESET}\n")

def pytest_terminal_summary(terminalreporter, exitstatus, config):
# Completely override the default summary
pass
Я пробовал несколько разных подходов, но ничего не добился.

Подробнее здесь: https://stackoverflow.com/questions/790 ... est-output
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»