Skip to content

Conversation

@semohr
Copy link
Contributor

@semohr semohr commented Sep 19, 2025

This PR enhances beets/logging.py with improved typing and tests:

  • getLogger now returns the precise logger type (BeetsLogger or RootLogger).
  • Tests use pytest and parametrize for more concise and readable coverage.

@semohr semohr requested a review from a team as a code owner September 19, 2025 13:29
Copilot AI review requested due to automatic review settings September 19, 2025 13:29
@semohr semohr added refactor typehints core Pull requests that modify the beets core `beets` labels Sep 19, 2025
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Sep 19, 2025

Reviewer's Guide

This PR refactors beets.logging.py to introduce precise type annotations (including overloads for getLogger, generics for log-safe conversion, and detailed signatures on StrFormatLogger methods) and modernizes test coverage by switching to pytest with parametrized inputs and caplog-based assertions.

File-Level Changes

Change Details Files
Enhanced typing in logging module
  • Imported TYPE_CHECKING, TypeVar, overload, ParamSpec, Any, Mapping, RootLogger
  • Added generic TypeVar T and ParamSpec P under TYPE_CHECKING
  • Annotated StrFormatLogger._log and _LogMessage constructor with explicit parameter types
  • Defined overloads for getLogger to return BeetsLogger or RootLogger
beets/logging.py
Refactored logsafe utility
  • Renamed logsafe to _logsafe and added generic return type
  • Replaced internal calls in str to use _logsafe
beets/logging.py
Adjusted StrFormatLogger._log behavior
  • Added stacklevel parameter to signature and call site
  • Wrapped msg in _LogMessage only when it is a str
  • Updated super()._log invocation to match new signature
beets/logging.py
Modernized logging tests to pytest
  • Replaced unittest.TestCase with plain functions and pytest.mark.parametrize
  • Introduced caplog fixture for record inspection
  • Consolidated multiple test cases into parameter sets for levels and message formats
test/test_logging.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@semohr semohr changed the title Improved beets.logging.py typing Improved beets/logging.py typing Sep 19, 2025
Copy link
Contributor

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 PR enhances the beets.logging.py module with improved typing annotations and modernizes the test suite. The primary purpose is to provide better type safety for developers using the beets logging system.

Key changes:

  • Added precise return type annotations for getLogger function using overloads
  • Modernized test suite to use pytest with parametrized testing
  • Added comprehensive type annotations throughout the logging module

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
beets/logging.py Added type annotations, overloads for getLogger, and improved function signatures
test/test_logging.py Converted from unittest to pytest with parametrized tests for better coverage
docs/changelog.rst Added changelog entry documenting the typing improvements
.github/CODEOWNERS Added specific ownership for metadata_plugins.py (unrelated to logging changes)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `beets/logging.py:98-107` </location>
<code_context>

     class _LogMessage:
-        def __init__(self, msg, args, kwargs):
+        def __init__(
+            self,
+            msg: str,
+            args: logging._ArgsType,
+            kwargs: dict[str, Any],
+        ):
             self.msg = msg
</code_context>

<issue_to_address>
**suggestion:** Using logging._ArgsType and dict[str, Any] may reduce compatibility with older Python versions.

Consider replacing logging._ArgsType with a public type like Sequence[Any], and dict[str, Any] with Dict[str, Any] for better compatibility with older Python versions.

```suggestion
from typing import Any, Sequence, Dict

    class _LogMessage:
        def __init__(
            self,
            msg: str,
            args: Sequence[Any],
            kwargs: Dict[str, Any],
        ):
            self.msg = msg
            self.args = args
            self.kwargs = kwargs
```
</issue_to_address>

### Comment 2
<location> `beets/logging.py:127` </location>
<code_context>

-        stacklevel = kwargs.pop("stacklevel", 1)
-        stacklevel = {"stacklevel": stacklevel}
+        if isinstance(msg, str):
+            msg = self._LogMessage(msg, args, kwargs)

</code_context>

<issue_to_address>
**suggestion:** The conditional wrapping of msg in _LogMessage may lead to inconsistent message handling.

Consider enforcing or documenting the expected type of msg to prevent inconsistent formatting when msg is not a str.

Suggested implementation:

```python
        """
        Log msg.format(*args, **kwargs)

        Args:
            msg (str): The log message format string. Must be a string.
        Raises:
            TypeError: If msg is not a string.
        """

```

```python
        if not isinstance(msg, str):
            raise TypeError(f"msg must be a str, got {type(msg).__name__}")
        msg = self._LogMessage(msg, args, kwargs)

```
</issue_to_address>

### Comment 3
<location> `beets/logging.py:177-180` </location>
<code_context>

-# Override the `getLogger` to use our machinery.
-def getLogger(name=None):  # noqa
+@overload
+def getLogger(name: str) -> BeetsLogger: ...
+@overload
+def getLogger(name: None = ...) -> RootLogger: ...
+def getLogger(name=None) -> BeetsLogger | RootLogger:  # noqa: N802
     if name:
</code_context>

<issue_to_address>
**suggestion:** Overload signatures for getLogger may not match runtime behavior exactly.

The implementation returns Logger.root when None is passed, which may not be typed as RootLogger. Please align the runtime return type with the overloads or update the overloads for consistency.
</issue_to_address>

### Comment 4
<location> `test/test_logging.py:65` </location>
<code_context>
+        with caplog.at_level(level, logger="test_logger"):
+            logger.log(level, msg, *args, **kwargs)
+
+        assert str(caplog.records[0].msg) == expected


</code_context>

<issue_to_address>
**suggestion (testing):** Assert that all log records are as expected, not just the first.

Only the first log record is asserted. If multiple logs are expected, iterate through all records or assert the total count to ensure accuracy.

```suggestion
        # Assert that all log records are as expected
        assert len(caplog.records) == 1, f"Expected 1 log record, got {len(caplog.records)}"
        for record in caplog.records:
            assert str(record.msg) == expected
```
</issue_to_address>

### Comment 5
<location> `beets/logging.py:182-185` </location>
<code_context>
def getLogger(name=None) -> BeetsLogger | RootLogger:  # noqa: N802
    if name:
        return my_manager.getLogger(name)  # type: ignore[return-value]
    else:
        return Logger.root

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))

```suggestion
    return my_manager.getLogger(name) if name else Logger.root
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Sep 19, 2025

Codecov Report

❌ Patch coverage is 80.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.92%. Comparing base (689ec10) to head (89c2e10).
⚠️ Report is 7 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
beets/logging.py 78.57% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6032      +/-   ##
==========================================
+ Coverage   66.71%   66.92%   +0.20%     
==========================================
  Files         117      117              
  Lines       18128    18133       +5     
  Branches     3073     3076       +3     
==========================================
+ Hits        12095    12135      +40     
+ Misses       5378     5338      -40     
- Partials      655      660       +5     
Files with missing lines Coverage Δ
beetsplug/fetchart.py 73.75% <ø> (ø)
beetsplug/lyrics.py 86.61% <100.00%> (+8.11%) ⬆️
beets/logging.py 94.44% <78.57%> (-5.56%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@semohr semohr enabled auto-merge September 30, 2025 11:43
@semohr semohr merged commit b06f3f6 into master Sep 30, 2025
19 checks passed
@semohr semohr deleted the logging++ branch September 30, 2025 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Pull requests that modify the beets core `beets` refactor typehints

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants