Циклический импорт во время `dictConfig` в журнале PythonPython

Программы на Python
Anonymous
Циклический импорт во время `dictConfig` в журнале Python

Сообщение Anonymous »

У меня есть специальный регистратор с приведенной ниже структурой. Он использует dictConfig для чтения файла default.json для регистратора. Он работает нормально, но когда я захотел вложить его в корень проекта, при его импорте возникла ошибка. Есть ли причина, почему это может произойти? Я дважды проверил путь и убедился, что он правильный.
Исходная структура
tealogger/
- configuration/
- default.json
- __init__.py
- formatter.py
- ...

Вложенная структура
projectroot/
- tealogger/
- configuration/
- default.json
- __init__.py
- formatter.py
- ...

Ошибка
Traceback (most recent call last):
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 400, in resolve
found = getattr(found, frag)
^^^^^^^^^^^^^^^^^^^^
AttributeError: cannot access submodule 'tealogg' of module 'projectroot' (most likely due to a circular import)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 552, in configure
formatters[name] = self.configure_formatter(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 664, in configure_formatter
result = self.configure_custom(config)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 479, in configure_custom
c = self.resolve(c)
^^^^^^^^^^^^^^^
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 403, in resolve
found = getattr(found, frag)
^^^^^^^^^^^^^^^^^^^^
AttributeError: cannot access submodule 'tealogg' of module 'projectroot' (most likely due to a circular import)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "", line 1, in
File "/Users/yk/Desktop/Project/Repository/Adobe/3DIQE/projectroot/projectroot/tealogger/__init__.py", line 128, in
tealogger = TeaLogger(
^^^^^^^
File "/Users/yk/Desktop/Project/Repository/Adobe/3DIQE/projectroot/projectroot/tealogger/__init__.py", line 100, in __new__
logging.config.dictConfig(DEFAULT_CONFIGURATION)
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 823, in dictConfig
dictConfigClass(config).configure()
File "/opt/python/substance3d_scene_automation_110_m_1089/lib/python3.11/logging/config.py", line 555, in configure
raise ValueError('Unable to configure '
ValueError: Unable to configure formatter 'color'

Сокращенная версия кода приведена ниже, полную версию можно найти на GitHub. Полученный мной результат можно воспроизвести с помощью ветки development-nest. Я просто запустил оболочку Python и запустил программу импорта в корне проекта.
projectroot/tealogger/__init__.py
import json
import logging
import logging.config
from pathlib import Path
from typing import Union

# Log Level
CRITICAL = logging.CRITICAL
FATAL = logging.FATAL
ERROR = logging.ERROR
WARNING = logging.WARNING
WARN = logging.WARN
INFO = logging.INFO
DEBUG = logging.DEBUG
NOTSET = logging.NOTSET

# Default
DEFAULT_CONFIGURATION = None
CURRENT_MODULE_PATH = Path(__file__).parent.expanduser().resolve()
with open(
CURRENT_MODULE_PATH / 'configuration' / 'default.json',
mode='r',
encoding='utf-8'
) as file:
DEFAULT_CONFIGURATION = json.load(file)

class TeaLogger(logging.Logger):
def __new__(cls, name: Union[str, None] = None, level: Union[int, str] = NOTSET, **kwargs):
if kwargs.get('dictConfig'):
# Dictionary
logging.config.dictConfig(kwargs.get('dictConfig'))
elif kwargs.get('fileConfig'):
# File
...
else:
# Default

if 'loggers' not in DEFAULT_CONFIGURATION:
DEFAULT_CONFIGURATION['loggers'] = {}
if name not in DEFAULT_CONFIGURATION['loggers']:
# Configure new logger with default configuration
DEFAULT_CONFIGURATION['loggers'][name] = {
'propagate': kwargs.get('propagate', False),
'handlers': kwargs.get('handler_list', ['default'])
}

# configuration['loggers'][name]['handlers'] = kwargs.get('handler_list')

# NOTE: Override only individual configuration!
# Overriding the entire configuration will cause this child
# logger to inherit any missing configuration from the root
# logger. (Even if the configuration was set previously.)
DEFAULT_CONFIGURATION['loggers'][name]['level'] = logging.getLevelName(level)
# configuration['loggers'][name]['level'] = level

logging.config.dictConfig(DEFAULT_CONFIGURATION)

# Get (Create) the Logger
tea = logging.getLogger(name)

return tea

def __init__(self, name: str, level: Union[int, str] = NOTSET) -> None:
super().__init__(self, name=name, level=level)

tea = TeaLogger(name=__name__, level=WARNING)

def set_level(level: Union[int, str] = NOTSET):
tea.setLevel(level)

def log(level: Union[int, str], message: str, *args, **kwargs):
if isinstance(level, str):
level = logging.getLevelName(level)
tea.log(level=level, msg=message, *args, **kwargs)

def debug(message: str, *args, **kwargs):
tea.debug(message, *args, **kwargs)

...

projectroot/tealogger/configuration/default.json
{
"version": 1,
"formatters": {
"default": {
"format": "[%(levelname)s %(name)s %(asctime)s] %(message)s",
"datefmt": "%Y-%m-%dT%H:%M:%S%z"
},
"short": {
"format": "[%(levelname)-.1s %(asctime)s] %(message)s",
"datefmt": "%Y-%m-%dT%H:%M:%S%z"
},
"color": {
"()": "projectroot.tealogger.formatter.ColorFormatter",
"record_format": "[%(levelname)s %(name)s %(asctime)s] %(message)s",
"date_format": "%Y-%m-%dT%H:%M:%S%z"
}
},
"filters": {
"stdout": {
"()" : "projectroot.tealogger.filter.StandardOutFilter"
}
},
"handlers": {
"default": {
"class": "logging.StreamHandler",
"formatter": "default",
"filters": [],
"stream": "ext://sys.stdout"
},
"console": {
"class": "logging.StreamHandler",
"formatter": "color",
"filters": [],
"stream": "ext://sys.stdout"
},
"stdout": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "color",
"filters": [
"stdout"
],
"stream": "ext://sys.stdout"
},
"stderr": {
"class": "logging.StreamHandler",
"level": "ERROR",
"formatter": "color",
"filters": [],
"stream": "ext://sys.stderr"
}
},
"loggers": {
"base": {
"level": "WARNING",
"propagate": false,
"filters": [],
"handlers": [
"stderr",
"stdout"
]
},
"tealogger": {
"level": "WARNING",
"propagate": false,
"filters": [],
"handlers": [
"console"
]
},
"tealogger.test.conftest": {
"level": "DEBUG",
"propagate": false,
"filters": [],
"handlers": [
"stderr",
"stdout"
]
}
},
"root": {
"level": "WARNING",
"filters": [],
"handlers": [
"default"
]
},
"incremental": false,
"disable_existing_loggers": false
}

projectroot/tealogger/formatter.py
import logging
from typing import Union

ESC = '\x1b['

_COLOR_CODE = {
# Reset
'RESET': f'{ESC}0m',
# Foreground
'FOREGROUND_BLACK': f'{ESC}30m',
...
}

_LEVEL_COLOR_CODE = {
'NOTSET': _COLOR_CODE['RESET'],
'DEBUG': _COLOR_CODE['FOREGROUND_CYAN'],
'INFO': _COLOR_CODE['FOREGROUND_GREEN'],
'WARNING': _COLOR_CODE['FOREGROUND_YELLOW'],
'SUCCESS': _COLOR_CODE['FOREGROUND_GREEN'],
'ERROR': _COLOR_CODE['FOREGROUND_RED'],
'CRITICAL': f"{_COLOR_CODE['FOREGROUND_RED']}{_COLOR_CODE['BACKGROUND_WHITE']}",
}

class ColorFormatter(logging.Formatter):
def __init__(self, record_format: Union[str, None] = None, date_format: Union[str, None] = None) -> None:
super().__init__(fmt=record_format, datefmt=date_format)

self._level_format = {
logging.DEBUG: (
f"{_LEVEL_COLOR_CODE['DEBUG']}"
f"{record_format}"
f"{_LEVEL_COLOR_CODE['NOTSET']}"
),
logging.INFO: (
f"{_LEVEL_COLOR_CODE['INFO']}"
f"{record_format}"
f"{_LEVEL_COLOR_CODE['NOTSET']}"
),
...
}

self._date_format = date_format

def format(
self,
record: logging.LogRecord
) -> str:
...



Подробнее здесь: https://stackoverflow.com/questions/789 ... on-logging

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