Skip to content

Conversation

@bybatkhuu
Copy link
Owner

This pull request refactors the way default log handlers are managed in the logging configuration module. The changes improve clarity by renaming the internal function, updating its usage throughout the code, and ensuring that user-provided handlers are always merged with the defaults.

Refactoring of log handler defaults:

  • Renamed the internal function _get_handlers to get_default_handlers for better clarity and made it available for import.
  • Updated the LoggerConfigPM class to use the new get_default_handlers function as the default factory for the handlers field, replacing the old _get_handlers.
  • Modified the _check_handlers validator to always merge user-supplied handlers with the default handlers, ensuring all defaults are present unless explicitly overridden. [1] [2]
  • Exported get_default_handlers in the module’s __all__ list to make it accessible for external use.

@bybatkhuu bybatkhuu self-assigned this Feb 2, 2026
Copilot AI review requested due to automatic review settings February 2, 2026 03:59
@bybatkhuu bybatkhuu added the patch [🐛 Fixes] PATCH version label Feb 2, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors the default log handler management by renaming _get_handlers to get_default_handlers, making it public, and changing the behavior to always merge user-provided handlers with defaults. The changes improve code clarity but introduce a significant behavioral change that affects how handlers are configured.

Changes:

  • Renamed _get_handlers to get_default_handlers and made it publicly exportable
  • Updated LoggerConfigPM.handlers field to use the renamed function in its default_factory
  • Modified _check_handlers validator to always merge user-provided handlers with defaults, ensuring all default handlers are present unless explicitly overridden
Comments suppressed due to low confidence (1)

src/beans_logging/config.py:17

  • The docstring for get_default_handlers states "Get default log handlers" but doesn't explain what these defaults are or when this function should be called by external users (since it's now exported). Consider enhancing the docstring to include:
  • A brief description of what the default handlers are
  • When/why external users might want to call this function
  • An example of the returned structure

This is especially important since the function is now part of the public API (exported in all).

    """Get default log handlers.

    Returns:
        dict[str, LogHandlerPM]: Default handlers as dictionary.
    """

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


__all__ = [
"LoggerConfigPM",
"get_default_handlers",
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function get_default_handlers is now exported in the config module's all list, but it's not re-exported in the main package init.py. This means users cannot import it as from beans_logging import get_default_handlers, but must use from beans_logging.config import get_default_handlers.

If this function is intended to be part of the public API (which the export suggests), consider adding it to src/beans_logging/init.py's imports and all list for easier access. Otherwise, if it's only meant for internal use within the config module, the export may be unnecessary.

Suggested change
"get_default_handlers",

Copilot uses AI. Check for mistakes.
elif isinstance(_handler, dict):
val[_i] = LogHandlerPM(**_handler)

val = {**_default_handlers, **val}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validator now always merges user-provided handlers with default handlers, which is a breaking behavioral change. Previously, if a user provided any handlers, only those handlers would be used. Now, all default handlers will always be included, with user-provided handlers overriding matching keys. This means users who previously provided a minimal set of handlers will now get all default handlers in addition to their custom ones.

Consider:

  1. Document this breaking change in the CHANGELOG or release notes
  2. Consider if this is the intended behavior - users may want to completely replace handlers, not just merge with defaults
  3. If merging is desired, consider adding an option to allow users to opt out of including defaults

Copilot uses AI. Check for mistakes.
default: DefaultConfigPM = Field(default_factory=DefaultConfigPM)
intercept: InterceptConfigPM = Field(default_factory=InterceptConfigPM)
handlers: dict[str, LogHandlerPM] = Field(default_factory=_get_handlers)
handlers: dict[str, LogHandlerPM] = Field(default_factory=get_default_handlers)
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the validator using mode="before", when a user doesn't provide handlers, the validator will receive None or an undefined value (not the result of default_factory), check "if not val" at line 142, and return _default_handlers. This means the default_factory=get_default_handlers on line 134 will never actually be invoked, making it redundant.

In Pydantic v2, validators with mode="before" execute before default values are applied, so the default_factory is bypassed when the validator returns a value for a missing field.

Consider either:

  1. Removing the default_factory since the validator now handles the default case
  2. Changing the validator to mode="after" if you want both the factory and validator to run, though this would change the merging logic

The current implementation works correctly but has unnecessary redundancy.

Suggested change
handlers: dict[str, LogHandlerPM] = Field(default_factory=get_default_handlers)
handlers: dict[str, LogHandlerPM] = Field()

Copilot uses AI. Check for mistakes.
Comment on lines +139 to 166
def _check_handlers(cls, val: Any) -> dict[str, LogHandlerPM]:
_default_handlers = get_default_handlers()

if not val:
return _default_handlers

if not isinstance(val, dict):
raise TypeError(
f"'handlers' attribute type {type(val).__name__} is invalid, must be a dict of <LogHandlerPM>, "
f"<LoguruHandlerPM> or dict!"
)

for _i, _handler in val.items():
if not isinstance(_handler, (LogHandlerPM, LoguruHandlerPM, dict)):
raise TypeError(
f"'handlers' attribute type {type(val).__name__} is invalid, must be a dict of <LogHandlerPM>, "
f"<LoguruHandlerPM> or dict!"
f"'handlers' attribute index {_i} type {type(_handler).__name__} is invalid, must be "
f"<LogHandlerPM>, <LoguruHandlerPM> or dict!"
)

for _i, _handler in val.items():
if not isinstance(_handler, (LogHandlerPM, LoguruHandlerPM, dict)):
raise TypeError(
f"'handlers' attribute index {_i} type {type(_handler).__name__} is invalid, must be "
f"<LogHandlerPM>, <LoguruHandlerPM> or dict!"
)

if isinstance(_handler, LoguruHandlerPM):
val[_i] = LogHandlerPM(
**_handler.model_dump(exclude_none=True, exclude_unset=True)
)
elif isinstance(_handler, dict):
val[_i] = LogHandlerPM(**_handler)
if isinstance(_handler, LoguruHandlerPM):
val[_i] = LogHandlerPM(
**_handler.model_dump(exclude_none=True, exclude_unset=True)
)
elif isinstance(_handler, dict):
val[_i] = LogHandlerPM(**_handler)

val = {**_default_handlers, **val}
return val
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new behavior where user-provided handlers are always merged with default handlers is not covered by tests. Consider adding tests that verify:

  1. When no handlers are provided, all defaults are present
  2. When user provides handlers with same keys as defaults, user values override defaults
  3. When user provides handlers with different keys, both defaults and user handlers are present
  4. The newly exported get_default_handlers function works correctly

This is important to test since this is a significant behavioral change in how handlers are managed.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch [🐛 Fixes] PATCH version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants