Skip to content

fix: Add type annotations in logging module#3757

Merged
johanneskoester merged 6 commits intosnakemake:mainfrom
jlumpe:logging-typing
Oct 1, 2025
Merged

fix: Add type annotations in logging module#3757
johanneskoester merged 6 commits intosnakemake:mainfrom
jlumpe:logging-typing

Conversation

@jlumpe
Copy link
Copy Markdown
Contributor

@jlumpe jlumpe commented Sep 28, 2025

Was trying to understand the logging system better and ended up filling in most of the missing type annotations. These were mostly pretty clear, but there are a few potential issues:

  • The first argument of is_quiet_about() and the quiet attribute of DefaultFormatter and DefaultFilter were annotated as Quietness, which does not seem correct. I have changed these to Container[Quietness].
  • LoggerManager is a bit of a difficult case because a lot of attributes are None before setup() is called. I have annotated these with Optional, but it might make more sense to do the typing as if setup() had already been called, even if it's not technically correct just after construction.

Also corrected the docstring of get_event_level().

QC

  • The PR contains a test case for the changes or the changes are already covered by an existing test case.
  • The documentation (docs/) is updated to reflect the changes or this is not necessary (e.g. if the change does neither modify the language nor the behavior or functionalities of Snakemake).

I ran the test suite applying typeguard to the logging module. Nearly every function with added/changed annotations was covered.

Summary by CodeRabbit

  • New Features

    • Improved logging output: clearer messages, enhanced color/TTY handling, and finer control over quietness and failed-log visibility.
  • Refactor

    • Standardized and strengthened public logging interfaces, handlers, formatters, and filters for more predictable terminal and file output.
  • Documentation

    • Updated logging docs to reflect clarified behaviors and configuration options.
  • Chores

    • Broad typing and public API alignment to improve maintainability without changing user-facing behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 28, 2025

📝 Walkthrough

Walkthrough

Adds comprehensive type annotations and public attribute declarations to logging components in src/snakemake/logging.py. Function and method signatures now use explicit types (including Optional and Collection) and concrete return types; constructors and helper methods for formatters, filters, handlers, and LoggerManager are retyped. No behavioral changes indicated.

Changes

Cohort / File(s) Summary of changes
Typing & public API annotations
src/snakemake/logging.py
Added Collection and TextIO imports; annotated functions (timestamp, format_dict, format_percentage, get_event_level, is_quiet_about) with explicit parameter and return types. Declared public attributes and typed constructors/methods for DefaultFormatter, DefaultFilter, ColorizingTextHandler. Retyped LoggerManager internals and factory methods (_configure_plugin_handler, _default_filter, _default_formatter, _default_filehandler, _default_streamhandler, setup, setup_logfile, cleanup_logfile, stop) and other internal methods to concrete return types.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.04% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "fix: Add type annotations in logging module" is fully related to the main change in the changeset. The raw summary shows extensive additions of type annotations throughout the logging.py file, including explicit type hints for function parameters, return types, and class attributes. The title clearly and concisely communicates the primary purpose of the changes, which is adding type annotations to improve type safety in the logging module. While the title uses "fix:" as a prefix (which might suggest a bug fix), the substantive description accurately captures the main technical work performed.
Description Check ✅ Passed The pull request description is comprehensive and well-structured. It provides clear context explaining the motivation for the changes, identifies specific typing decisions made (like changing Quietness to Container[Quietness]), highlights potential issues with the approach (LoggerManager attributes being None before setup()), and includes methodological details about testing with typeguard. Both QC checklist items are completed with appropriate justification. The description also notes a docstring correction to get_event_level(), which aligns with the changes shown in the raw summary.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8920419 and 957df04.

📒 Files selected for processing (1)
  • src/snakemake/logging.py (16 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/snakemake/logging.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/snakemake/logging.py (3)

90-106: Guard against division by zero in format_percentage()

total can be 0 (e.g., empty DAG), which raises ZeroDivisionError. Add a fast path for non-positive totals.

Apply this diff:

 def format_percentage(done: int, total: int) -> str:
     """Format percentage from given fraction while avoiding superfluous precision."""
+    # Avoid division by zero / invalid totals
+    if total <= 0:
+        return "0%"
     if done == total:
         return "100%"
     if done == 0:
         return "0%"

108-121: Docstring return type mismatch in get_event_level()

Annotation is tuple[Optional[LogEvent], str] but docstring says tuple[LogEvent, str]. Align the docstring.

Apply this diff:

-    Returns:
-        tuple[LogEvent, str]
+    Returns:
+        tuple[Optional[LogEvent], str]

390-399: Dry-run filter condition never matches

level is record.levelname (e.g., "INFO"), so comparing to "run_info" is always false; run-info messages won’t be allowed through during dry runs as intended. Compare the event instead.

Apply this diff:

-        if self.dryrun and level == "run_info":
+        if self.dryrun and event == LogEvent.RUN_INFO:
             return True
🧹 Nitpick comments (6)
src/snakemake/logging.py (6)

509-512: Ensure is_tty returns a real bool

Currently returns None when stream lacks isatty, violating the -> bool contract (and confusing type checkers). Coerce to bool.

Apply this diff:

 @property
 def is_tty(self) -> bool:
     isatty = getattr(self.stream, "isatty", None)
-    return isatty and isatty()
+    return bool(isatty and isatty())

647-654: Assert settings is initialized where required

These helpers assume settings is set by setup(); add an assert to placate static checkers and fail fast if misused.

Apply this diff:

 def _default_filter(self) -> DefaultFilter:
+    assert self.settings is not None, "LoggerManager.settings not initialized; call setup() first."
     return DefaultFilter(
         self.settings.quiet,
         self.settings.debug_dag,
         self.settings.dryrun,
         self.settings.printshellcmds,
     )

655-660: Same here: guard self.settings in _default_formatter()

Apply this diff:

 def _default_formatter(self) -> DefaultFormatter:
+    assert self.settings is not None, "LoggerManager.settings not initialized; call setup() first."
     return DefaultFormatter(
         self.settings.quiet,
         self.settings.show_failed_logs,
     )

661-669: And in _default_filehandler(): guard settings before use

Apply this diff:

 def _default_filehandler(self, logfile) -> logging.Handler:
+    assert self.settings is not None, "LoggerManager.settings not initialized; call setup() first."
     logfile_handler = logging.FileHandler(logfile)
     logfile_handler.setFormatter(self._default_formatter())
     logfile_handler.addFilter(self._default_filter())
     logfile_handler.setLevel(
         logging.DEBUG if self.settings.verbose else logging.INFO
     )

671-680: And in _default_streamhandler(): guard settings before use

Apply this diff:

 def _default_streamhandler(self) -> logging.Handler:
+    assert self.settings is not None, "LoggerManager.settings not initialized; call setup() first."
     stream_handler = ColorizingTextHandler(
         nocolor=self.settings.nocolor,
         stream=sys.stdout if self.settings.stdout else sys.stderr,
         mode=self.mode,
     )

723-726: Avoid touching private attribute _thread in stop()

Relying on a private attribute is brittle; QueueListener.stop() is safe after start(). Check for None only.

Apply this diff:

 def stop(self) -> None:
-    if self.queue_listener is not None and self.queue_listener._thread is not None:
-        self.queue_listener.stop()
+    if self.queue_listener is not None:
+        self.queue_listener.stop()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdd824b and 3eae68e.

📒 Files selected for processing (1)
  • src/snakemake/logging.py (16 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of the self argument of methods.
Do not suggest type annotation of the cls argument of classmethods.
Do not suggest return type annotation if a function or method does not contain a return statement.

Files:

  • src/snakemake/logging.py
🔇 Additional comments (3)
src/snakemake/logging.py (3)

32-35: LGTM: precise return type for timestamp()

Clear and correct.


123-129: LGTM: is_quiet_about() signature and logic

Accepting Collection[Quietness] and parsing msg_type is appropriate.


132-141: LGTM: DefaultFormatter attribute typing and constructor

Optional quiet handled correctly with set() default.

Comment thread src/snakemake/logging.py
Comment thread src/snakemake/logging.py
@cademirch cademirch changed the title Add type annotations in logging module fix: Add type annotations in logging module Sep 28, 2025
@cademirch cademirch self-requested a review September 28, 2025 20:06
Copy link
Copy Markdown
Contributor

@cademirch cademirch left a comment

Choose a reason for hiding this comment

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

Thanks for adding the type hints! If you could add the one coderabbit suggestion, as well as handle the merge conflicts that would be great!

Comment thread src/snakemake/logging.py
@jlumpe
Copy link
Copy Markdown
Contributor Author

jlumpe commented Sep 28, 2025

  • Removed incorrect filter annotation ColorizingTextHandler as pointed out by CodeRabbit. Also removed formatter, which wasn't technically wrong but wasn't required over base class.
  • Corrected LoggerManager.logfile_handlers from dict[LogHandlerBase, str] to dict[logging.Handler, str].
  • Also removed "Args" and "Returns" sections in docstring of get_event_level() - these contained only types and not descriptions.
  • Per CodeRabbit, fixed ColorizingTextHandler.is_tty to always return bool.
  • Resolved merge conflicts.

One more potential issue that CodeRabbit pointed out: in DefaultFilter.filter(), record.levelname is checked for equality against "run_info" when it should probably be checking record.event.

@cademirch
Copy link
Copy Markdown
Contributor

  • Removed incorrect filter annotation ColorizingTextHandler as pointed out by CodeRabbit. Also removed formatter, which wasn't technically wrong but wasn't required over base class.
  • Corrected LoggerManager.logfile_handlers from dict[LogHandlerBase, str] to dict[logging.Handler, str].
  • Also removed "Args" and "Returns" sections in docstring of get_event_level() - these contained only types and not descriptions.
  • Per CodeRabbit, fixed ColorizingTextHandler.is_tty to always return bool.
  • Resolved merge conflicts.

Great! Thanks.

One more potential issue that CodeRabbit pointed out: in DefaultFilter.filter(), record.levelname is checked for equality against "run_info" when it should probably be checking record.event.

I'm inclined to agree, but am curious what (if any) change in behavior this will result in.

Copy link
Copy Markdown
Contributor

@cademirch cademirch left a comment

Choose a reason for hiding this comment

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

Please fix formatting, then ready to merge as long as tests pass!

@jlumpe
Copy link
Copy Markdown
Contributor Author

jlumpe commented Sep 29, 2025

Formatting fixed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/snakemake/logging.py (1)

380-421: Critical bug: incorrect comparison at line 384.

Line 384 compares level (which is record.levelname) to the string "run_info":

if self.dryrun and level == "run_info":

However, record.levelname contains standard Python logging levels (INFO, DEBUG, WARNING, ERROR, CRITICAL) and will never equal "run_info". This condition will never be True, breaking the intended dryrun filtering logic.

Fix: Compare event to LogEvent.RUN_INFO instead:

if self.dryrun and event == LogEvent.RUN_INFO:

This matches the pattern used elsewhere in the codebase (e.g., line 535: if level == "INFO" and event in self.yellow_info_events:).

🧹 Nitpick comments (1)
src/snakemake/logging.py (1)

563-587: Consider adding explicit return type annotation.

The parameter type annotations are correct, but the method contains return statements (line 587) and could benefit from an explicit -> None return type annotation for completeness.

Apply this diff:

     def setup(
         self,
         handlers: List[LogHandlerBase],
         settings: OutputSettingsLoggerInterface,
-    ):
+    ) -> None:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6b317 and 8920419.

📒 Files selected for processing (1)
  • src/snakemake/logging.py (14 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of the self argument of methods.
Do not suggest type annotation of the cls argument of classmethods.
Do not suggest return type annotation if a function or method does not contain a return statement.

Files:

  • src/snakemake/logging.py
🪛 Ruff (0.13.1)
src/snakemake/logging.py

451-451: Undefined name ExecMode

(F821)


550-550: Undefined name ExecMode

(F821)

🔇 Additional comments (20)
src/snakemake/logging.py (20)

22-22: LGTM!

The addition of Collection and TextIO to the typing imports is appropriate and necessary for the type annotations introduced throughout the module.


31-33: LGTM!

The return type annotation -> str correctly reflects that timestamp() returns a formatted string.


89-104: LGTM!

The parameter and return type annotations are accurate for this percentage formatting function.


107-110: LGTM!

The updated signature and simplified docstring are improvements. Moving type information from docstring to the signature aligns with modern Python typing practices.


113-118: LGTM!

Broadening the quiet parameter from Quietness to Collection["Quietness"] correctly reflects that the function accepts any collection of quietness values.


121-132: LGTM!

The class attribute declarations and constructor signature correctly document the public API. The use of Optional[Collection["Quietness"]] with a None-safe default is appropriate.


134-158: LGTM!

The format method's type annotation for the record parameter is correct.


360-379: LGTM!

The class attribute declarations and constructor signature correctly document the DefaultFilter API with appropriate typing.


490-492: LGTM!

Explicitly returning bool from is_tty resolves the previous review concern and ensures the property always returns a boolean type.


494-524: LGTM!

The type annotation for the record parameter in the emit method is correct.


526-543: LGTM!

The type annotations for both parameters in the decorate method are appropriate.


547-562: LGTM with one caveat.

The class attribute declarations correctly document the LoggerManager's public API. The use of Optional is appropriate since these attributes are None before setup() is called. The correction of logfile_handlers to dict[logging.Handler, str] (from the narrower dict[LogHandlerBase, str]) properly accommodates both plugin handlers and standard FileHandler instances.

Note: The mode attribute at line 550 has the same ExecMode undefined issue flagged in ColorizingTextHandler.


634-639: LGTM!

The return type annotation correctly reflects that this method returns the plugin handler it configured.


641-647: LGTM!

The return type annotation is specific and accurate.


649-653: LGTM!

The return type annotation is specific and accurate.


655-663: LGTM!

The return type logging.Handler is appropriate for this factory method that creates a FileHandler.


665-673: LGTM!

The return type logging.Handler is appropriate for this factory method that creates a ColorizingTextHandler.


686-690: LGTM!

The return type annotation -> None is correct for this cleanup method.


692-712: LGTM!

The parameter and return type annotations are appropriate. The use of Optional[os.PathLike] correctly indicates that workdir can be None.


714-716: LGTM!

The return type annotation -> None is correct for this teardown method.

Comment thread src/snakemake/logging.py
@jlumpe
Copy link
Copy Markdown
Contributor Author

jlumpe commented Sep 29, 2025

Aaand fixed the remaining ExecMode-related attribute annotations that should have been removed in the merge, added CodeRabbit's additional suggestion for a return value annotation, plus a couple more return annotations for good measure.

@johanneskoester johanneskoester merged commit 63404b2 into snakemake:main Oct 1, 2025
60 checks passed
johanneskoester pushed a commit that referenced this pull request Oct 1, 2025
🤖 I have created a release *beep* *boop*
---


##
[9.11.7](v9.11.6...v9.11.7)
(2025-10-01)


### Bug Fixes

* Add type annotations in logging module
([#3757](#3757))
([63404b2](63404b2))
* source_path not mounted
([#3738](#3738))
([86e30b3](86e30b3))


### Documentation

* mastodon announcements
([#3723](#3723))
([7da1b18](7da1b18))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
kjohnsen pushed a commit to kjohnsen/snakemake that referenced this pull request Dec 15, 2025
Was trying to understand the logging system better and ended up filling
in most of the missing type annotations. These were mostly pretty clear,
but there are a few potential issues:

- The first argument of `is_quiet_about()` and the `quiet` attribute of
`DefaultFormatter` and `DefaultFilter` were annotated as `Quietness`,
which does not seem correct. I have changed these to
`Container[Quietness]`.
- `LoggerManager` is a bit of a difficult case because a lot of
attributes are `None` before `setup()` is called. I have annotated these
with `Optional`, but it might make more sense to do the typing as if
`setup()` had already been called, even if it's not technically correct
just after construction.

Also corrected the docstring of `get_event_level()`.

### QC
<!-- Make sure that you can tick the boxes below. -->

* [x] The PR contains a test case for the changes or the changes are
already covered by an existing test case.
* [x] The documentation (`docs/`) is updated to reflect the changes or
this is not necessary (e.g. if the change does neither modify the
language nor the behavior or functionalities of Snakemake).

I ran the test suite applying
[typeguard](https://typeguard.readthedocs.io) to the logging module.
Nearly every function with added/changed annotations was covered.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Improved logging output: clearer messages, enhanced color/TTY
handling, and finer control over quietness and failed-log visibility.

- Refactor
- Standardized and strengthened public logging interfaces, handlers,
formatters, and filters for more predictable terminal and file output.

- Documentation
- Updated logging docs to reflect clarified behaviors and configuration
options.

- Chores
- Broad typing and public API alignment to improve maintainability
without changing user-facing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
kjohnsen pushed a commit to kjohnsen/snakemake that referenced this pull request Dec 15, 2025
🤖 I have created a release *beep* *boop*
---


##
[9.11.7](snakemake/snakemake@v9.11.6...v9.11.7)
(2025-10-01)


### Bug Fixes

* Add type annotations in logging module
([snakemake#3757](snakemake#3757))
([63404b2](snakemake@63404b2))
* source_path not mounted
([snakemake#3738](snakemake#3738))
([86e30b3](snakemake@86e30b3))


### Documentation

* mastodon announcements
([snakemake#3723](snakemake#3723))
([7da1b18](snakemake@7da1b18))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants