fixhunt

ruff G010

labelled unknown
Runs / 2026-10-17T20-24-12-113Z-iye4-ruff-G010ruff 0.17.12026-10-17 20:24 UTC8 programs × 2 round(s)16 programs, 12 fired, 12 fixed, 8 findings, 0 rejectedmodel cost $0.15

Analysis Nemotron 3 Super

The rule flags any use of the deprecated method `.warn()` on objects identified as loggers (including direct usage of `logging`, Flask’s `current_app.logger`, locally defined logger-like variables, and imported logger objects), suggesting replacement with the equivalent `.warning()` method.

Fix: Replace every occurrence of the method call `.warn(` with `.warning(` while preserving all arguments, formatting, and surrounding context exactly.

10 hazards
  • Rewriting `obj.warn` to `obj.warning` changes behavior if `obj` defines a custom `.warn` method distinct from `.warning` (e.g., in a test mock or wrapper class).
  • If `warn` is accessed via `getattr(obj, 'warn')` dynamically, the textual replacement does not affect the attribute lookup and may leave stale code.
  • When `warn` appears inside a string literal or comment, the tool should not modify it, but overzealous matching could corrupt documentation or logging configuration strings.
  • If `warn` is part of a larger identifier like `warning_prefix`, the replacement would produce `warning_prefixing`, causing a NameError or unintended variable reference.
  • In code using `logging.Logger.warn` via a bound method stored earlier (e.g., `log_method = logger.warn; log_method()`), rewriting the source does not update the stored reference, leading to continued use of the deprecated method.
  • If the logger object is conditionally assigned (e.g., `logger = debug_logger if DEBUG else null_logger`) and only one branch defines `.warn`, the rewrite may incorrectly apply to a fallback object lacking `.warning`.
  • When `lint.logger-objects` includes a logger imported via `from x import y as z`, the tool may fail to recognize the alias and either miss the violation or incorrectly rewrite non-logger uses of `z`.
  • If the code defines a local variable named `warn` in the same scope (e.g., `warn = 42; logger.warn()`), the rewrite could ambiguously interfere with that variable though unlikely due to attribute access syntax.
  • In Flask apps, if `current_app` is accessed outside an application context, the rewrite does not fix the underlying runtime error but may create false confidence in correctness.
  • When multiple logger-like objects exist (e.g., `access_logger` and `error_logger`), the tool might incorrectly apply the fix based on naming heuristics even if only one is a true logger per `lint.logger-objects`.

Findings

programoraclelabeljudgestatussummary
r2-p005fix-changes-behaviorlabelled safereal · mediumduplicateThe rule rewrites logger.warn to logger.warning assuming the logger object has a warning method, but the program uses a custom NullLogger class that lacks both…
r1-p001fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule rewrites the deprecated .warn() method to .warning() as documented. The stderr difference occurs because the DeprecationWarning emitted by the old cod…
r1-p002fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule replaces the deprecated `logger.warn` method with `logger.warning` as documented. The stderr difference occurs because the DeprecationWarning emitted …
r1-p007fix-changes-behaviorlabelled safeintended-change · lowno matchThe rule intentionally rewrites the deprecated `logger.warn` method to `logger.warning` to eliminate the DeprecationWarning, which is the documented purpose of…
r2-p003fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule rewrites the deprecated `logger.warn` call to `logger.warning`, which the documentation states is functionally equivalent. The observed difference is …
r2-p004fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule replaces the deprecated `logger.warn` with `logger.warning`, which the documentation states are functionally equivalent. The observed difference is th…
r2-p007fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule rewrites the deprecated `warn` method to `warning` to eliminate the DeprecationWarning, which is exactly the documented purpose. The stderr output cha…
r2-p008fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule rewrites the deprecated `logger.warn` call to `logger.warning` as documented. The stderr difference is exactly the removal of the DeprecationWarning t…

Programs

idresulttitlehazard
r1-p001fix-changes-behaviorMultiple logger-like objects - heuristic misapplicationWhen multiple logger-like objects exist (e.g., `access_logger` and `error_logger`), the tool might incorrectly apply the fix based on naming heuristics even if only one is a true logger per `lint.logg
r1-p002fix-changes-behaviorExceptions inside rewritten code with finally/else/chainingexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p003silentDynamic getattr lookup - rewrite misses attribute accessIf `warn` is accessed via `getattr(obj, 'warn')` dynamically, the textual replacement does not affect the attribute lookup and may leave stale code.
r1-p004fix okBound method stored earlier - stored reference not updatedIf `logging.Logger.warn` via a bound method stored earlier (e.g., `log_method = logger.warn; log_method()`), rewriting the source does not update the stored reference, leading to continued use of the
r1-p005fix okLocal variable named `warn` in same scopeIf the code defines a local variable named `warn` in the same scope (e.g., `warn = 42; logger.warn()`), the rewrite could ambiguously interfere with that variable though unlikely due to attribute acce
r1-p006fix okDict ordering, duplicate keys, __hash__/__eq__, setsdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r1-p007fix-changes-behavior`warn` as substring in larger identifierIf `warn` is part of a larger identifier like `warning_prefix`, the replacement would produce `warning_prefixing`, causing a NameError or unintended variable reference.
r1-p008fix okFile modes, truncation, partial writes, non-str datafile modes and truncation, partial writes, non-str data passed to write
r2-p001silent prediction offCustom warn method differs from warningRewriting obj.warn to obj.warning changes behavior if obj defines a custom .warn method distinct from .warning
r2-p002silentwarn inside string literal not rewrittenWhen warn appears inside a string literal or comment, the tool should not modify it, but overzealous matching could corrupt documentation or logging configuration strings
r2-p003fix-changes-behaviorString formatting edge cases with % and f-stringsstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r2-p004fix-changes-behaviorGenerator consumed once, logger.warn inside generator expressiongenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p005fix-changes-behaviorConditionally assigned logger missing .warning in fallbackIf the logger object is conditionally assigned (e.g., logger = debug_logger if DEBUG else null_logger) and only one branch defines .warn, the rewrite may incorrectly apply to a fallback object lacking
r2-p006silentDynamic getattr access to warn not affected by textual replaceIf warn is accessed via getattr(obj, 'warn') dynamically, the textual replacement does not affect the attribute lookup and may leave stale code.
r2-p007fix-changes-behaviorAliased logger import not recognized by lint.logger-objectsWhen lint.logger-objects includes a logger imported via from x import y as z, the tool may fail to recognize the alias and either miss the violation or incorrectly rewrite non-logger uses of z.
r2-p008fix-changes-behaviorBytes vs str in warn argument encoding handlingbytes vs str, encoding and newline handling, text vs binary modes

Sources

r1-p001 · Multiple logger-like objects - heuristic misapplication
1import logging
2# Simulate lint.logger-objects containing only ['error_logger']
3error_logger = logging.getLogger('error')
4access_logger = logging.getLogger('access') # Not in lint.logger-objects
5# Rule should fire on error_logger.warn but NOT on access_logger.warn
6try:
7 error_logger.warn('error msg') # Should become .warning
8 access_logger.warn('access msg') # Should NOT be changed (heuristic overreach)
9except Exception as e:
10 print(f'Error: {type(e).__name__}: {e}')
11print('Done')
--- orig.py	2026-10-17 16:24:27.826896136 -0400
+++ fixed.py	2026-10-17 16:24:28.077895473 -0400
@@ -4,8 +4,8 @@
 access_logger = logging.getLogger('access')  # Not in lint.logger-objects
 # Rule should fire on error_logger.warn but NOT on access_logger.warn
 try:
-    error_logger.warn('error msg')   # Should become .warning
-    access_logger.warn('access msg') # Should NOT be changed (heuristic overreach)
+    error_logger.warning('error msg')   # Should become .warning
+    access_logger.warning('access msg') # Should NOT be changed (heuristic overreach)
 except Exception as e:
     print(f'Error: {type(e).__name__}: {e}')
 print('Done')
Done
r1-p002 · Exceptions inside rewritten code with finally/else/chaining
1import logging
2logger = logging.getLogger('test')
3try:
4 logger.warn('about to fail') # Rewritten to .warning
5 raise ValueError('bad')
6except ValueError as e:
7 try:
8 logger.warn('in except') # Rewritten
9 raise RuntimeError('chained') from e
10 except RuntimeError as final_err:
11 pass
12else:
13 logger.warn('no ex') # Rewritten
14finally:
15 logger.warn('cleanup') # Rewritten
16print('Finished')
--- orig.py	2026-10-17 16:24:28.224895086 -0400
+++ fixed.py	2026-10-17 16:24:28.482894406 -0400
@@ -1,16 +1,16 @@
 import logging
 logger = logging.getLogger('test')
 try:
-    logger.warn('about to fail')  # Rewritten to .warning
+    logger.warning('about to fail')  # Rewritten to .warning
     raise ValueError('bad')
 except ValueError as e:
     try:
-        logger.warn('in except')  # Rewritten
+        logger.warning('in except')  # Rewritten
         raise RuntimeError('chained') from e
     except RuntimeError as final_err:
         pass
 else:
-    logger.warn('no ex')  # Rewritten
+    logger.warning('no ex')  # Rewritten
 finally:
-    logger.warn('cleanup')  # Rewritten
+    logger.warning('cleanup')  # Rewritten
 print('Finished')
Finished
r1-p003 · Dynamic getattr lookup - rewrite misses attribute access
1import logging
2logger = logging.getLogger('test')
3method_name = 'warn'
4func = getattr(logger, method_name) # Gets .warn, not affected by source rewrite
5func('dynamic call') # Still uses deprecated .warn
6# Rule does NOT trigger on getattr(...) so no rewrite happens
7print('Called via getattr')
Called via getattr
r1-p004 · Bound method stored earlier - stored reference not updated
1import logging
2logger = logging.getLogger('test')
3log_method = logger.warn # Bound to deprecated .warn
4# Rule rewrites: logger.warn(...) -> logger.warning(...) but NOT log_method
5log_method('stored call') # Still uses .warn
6# New calls use .warning
7logger.warn('direct call') # Now .warning
8print('Stored vs direct')
--- orig.py	2026-10-17 16:24:28.838893468 -0400
+++ fixed.py	2026-10-17 16:24:29.085892819 -0400
@@ -4,5 +4,5 @@
 # Rule rewrites: logger.warn(...) -> logger.warning(...) but NOT log_method
 log_method('stored call')  # Still uses .warn
 # New calls use .warning
-logger.warn('direct call')  # Now .warning
+logger.warning('direct call')  # Now .warning
 print('Stored vs direct')
Stored vs direct
r1-p005 · Local variable named `warn` in same scope
1import logging
2logger = logging.getLogger('test')
3warn = 99 # Local variable, not interfering with attribute access
4# Rule rewrites: logger.warn(...) -> logger.warning(...)
5logger.warn('value:', warn) # arg 'warn' is the var 99
6print(f'Local warn={warn}')
--- orig.py	2026-10-17 16:24:29.227892446 -0400
+++ fixed.py	2026-10-17 16:24:29.478891788 -0400
@@ -2,5 +2,5 @@
 logger = logging.getLogger('test')
 warn = 99  # Local variable, not interfering with attribute access
 # Rule rewrites: logger.warn(...) -> logger.warning(...)
-logger.warn('value:', warn)  # arg 'warn' is the var 99
+logger.warning('value:', warn)  # arg 'warn' is the var 99
 print(f'Local warn={warn}')
Local warn=99
r1-p006 · Dict ordering, duplicate keys, __hash__/__eq__, sets
1import logging
2class WeirdDict(dict):
3 def __hash__(self): return id(self)
4 def __eq__(self, other): return self is other
5logger = logging.getLogger('test')
6d = {}
7d[WeirdDict()] = 'first'
8d[WeirdDict()] = 'second' # Different key due to __eq__
9# Rule rewrites both:
10logger.warn('dict len:', len(d)) # Becomes .warning
11logger.warn('weird:', d) # Becomes .warning
12print(f'dict size: {len(d)}')
--- orig.py	2026-10-17 16:24:29.621891413 -0400
+++ fixed.py	2026-10-17 16:24:29.858890792 -0400
@@ -7,6 +7,6 @@
 d[WeirdDict()] = 'first'
 d[WeirdDict()] = 'second'  # Different key due to __eq__
 # Rule rewrites both:
-logger.warn('dict len:', len(d))  # Becomes .warning
-logger.warn('weird:', d)        # Becomes .warning
+logger.warning('dict len:', len(d))  # Becomes .warning
+logger.warning('weird:', d)        # Becomes .warning
 print(f'dict size: {len(d)}')
dict size: 2
r1-p007 · `warn` as substring in larger identifier
1import logging
2logger = logging.getLogger('test')
3warning_prefix = 'pre-' # Variable containing substring 'warn'
4# Rule should ONLY replace .warn(...) -> .warning(...), NOT touch identifiers
5# So: logger.warn(warning_prefix + 'fix') -> logger.warning(warning_prefix + 'fix')
6# SAFE: no substring replacement in identifiers
7logger.warn(warning_prefix + 'fix') # Correctly becomes .warning
8print(f'Prefix: {warning_prefix}')
--- orig.py	2026-10-17 16:24:30.005890406 -0400
+++ fixed.py	2026-10-17 16:24:30.271889712 -0400
@@ -4,5 +4,5 @@
 # Rule should ONLY replace .warn(...) -> .warning(...), NOT touch identifiers
 # So: logger.warn(warning_prefix + 'fix') -> logger.warning(warning_prefix + 'fix')
 # SAFE: no substring replacement in identifiers
-logger.warn(warning_prefix + 'fix')  # Correctly becomes .warning
+logger.warning(warning_prefix + 'fix')  # Correctly becomes .warning
 print(f'Prefix: {warning_prefix}')
Prefix: pre-
r1-p008 · File modes, truncation, partial writes, non-str data
1import logging
2logger = logging.getLogger('test')
3with open('test_out.txt', 'w') as f:
4 f.write('start\n')
5 # Rule rewrites: logger.warn(...) -> logger.warning(...)
6 logger.warn('log to file', f) # Passing file obj as arg
7 f.write('middle\n')
8 logger.warn('more log')
9 f.write('end\n')
10with open('test_out.txt', 'r') as f:
11 print(f.read(), end='')
--- orig.py	2026-10-17 16:24:30.450889245 -0400
+++ fixed.py	2026-10-17 16:24:30.718888546 -0400
@@ -3,9 +3,9 @@
 with open('test_out.txt', 'w') as f:
     f.write('start\n')
     # Rule rewrites: logger.warn(...) -> logger.warning(...)
-    logger.warn('log to file', f)  # Passing file obj as arg
+    logger.warning('log to file', f)  # Passing file obj as arg
     f.write('middle\n')
-    logger.warn('more log')
+    logger.warning('more log')
     f.write('end\n')
 with open('test_out.txt', 'r') as f:
     print(f.read(), end='')
start
middle
end
r2-p001 · Custom warn method differs from warning
1class LoggerLike:
2 def warn(self, msg):
3 return f'WARN: {msg}'
4 def warning(self, msg):
5 return f'WARNING: {msg}'
6
7l = LoggerLike()
8print(l.warn('test'))
9print(l.warning('test'))
WARN: test
WARNING: test
r2-p002 · warn inside string literal not rewritten
1s = 'This is a warn message in a string'
2print(s)
3# This is a warn message in a comment
4print('No change expected')
This is a warn message in a string
No change expected
r2-p003 · String formatting edge cases with % and f-strings
1import logging
2logger = logging.getLogger()
3logger.setLevel(logging.INFO)
4name = 'Alice'
5logger.warn('Hello %s' % name)
6logger.warn(f'Hello {name}')
7logger.warn('Hello {}'.format(name))
--- orig.py	2026-10-17 16:24:36.413873903 -0400
+++ fixed.py	2026-10-17 16:24:36.668873256 -0400
@@ -2,6 +2,6 @@
 logger = logging.getLogger()
 logger.setLevel(logging.INFO)
 name = 'Alice'
-logger.warn('Hello %s' % name)
-logger.warn(f'Hello {name}')
-logger.warn('Hello {}'.format(name))
+logger.warning('Hello %s' % name)
+logger.warning(f'Hello {name}')
+logger.warning('Hello {}'.format(name))
r2-p004 · Generator consumed once, logger.warn inside generator expression
1import logging
2logger = logging.getLogger()
3logger.setLevel(logging.INFO)
4gen = (logger.warn(f'Processing {x}') for x in range(2))
5print('First consumption:', list(gen))
6print('Second consumption:', list(gen))
--- orig.py	2026-10-17 16:24:36.818872876 -0400
+++ fixed.py	2026-10-17 16:24:37.063872256 -0400
@@ -1,6 +1,6 @@
 import logging
 logger = logging.getLogger()
 logger.setLevel(logging.INFO)
-gen = (logger.warn(f'Processing {x}') for x in range(2))
+gen = (logger.warning(f'Processing {x}') for x in range(2))
 print('First consumption:', list(gen))
 print('Second consumption:', list(gen))
First consumption: [None, None]
Second consumption: []
r2-p005 · Conditionally assigned logger missing .warning in fallback
1DEBUG = False
2class DebugLogger:
3 def warn(self, msg):
4 print(f'DEBUG WARN: {msg}')
5class NullLogger:
6 pass # No warn or warning
7logger = DebugLogger() if DEBUG else NullLogger()
8try:
9 logger.warn('test')
10except AttributeError as e:
11 print(type(e).__name__, str(e))
--- orig.py	2026-10-17 16:24:37.206871895 -0400
+++ fixed.py	2026-10-17 16:24:37.375871469 -0400
@@ -6,6 +6,6 @@
     pass  # No warn or warning
 logger = DebugLogger() if DEBUG else NullLogger()
 try:
-    logger.warn('test')
+    logger.warning('test')
 except AttributeError as e:
     print(type(e).__name__, str(e))
AttributeError 'NullLogger' object has no attribute 'warn'
r2-p006 · Dynamic getattr access to warn not affected by textual replace
1import logging
2logger = logging.getLogger()
3logger.setLevel(logging.INFO)
4method = getattr(logger, 'warn')
5method('test via getattr')
6# After fix, source still has getattr(logger, 'warn') so behavior unchanged
r2-p007 · Aliased logger import not recognized by lint.logger-objects
1import logging
2my_logger = logging.getLogger()
3my_logger.setLevel(logging.INFO)
4my_logger.warn('test')
5print('Done')
--- orig.py	2026-10-17 16:24:37.698870654 -0400
+++ fixed.py	2026-10-17 16:24:37.943870036 -0400
@@ -1,5 +1,5 @@
 import logging
 my_logger = logging.getLogger()
 my_logger.setLevel(logging.INFO)
-my_logger.warn('test')
+my_logger.warning('test')
 print('Done')
Done
r2-p008 · Bytes vs str in warn argument encoding handling
1import logging
2import io
3logger = logging.getLogger()
4logger.setLevel(logging.INFO)
5handler = io.StringIO()
6logger.addHandler(logging.StreamHandler(handler))
7logger.warn(b'byte string')
8logger.warn('str string')
9print(handler.getvalue())
--- orig.py	2026-10-17 16:24:38.071869714 -0400
+++ fixed.py	2026-10-17 16:24:38.323869081 -0400
@@ -4,6 +4,6 @@
 logger.setLevel(logging.INFO)
 handler = io.StringIO()
 logger.addHandler(logging.StreamHandler(handler))
-logger.warn(b'byte string')
-logger.warn('str string')
+logger.warning(b'byte string')
+logger.warning('str string')
 print(handler.getvalue())
b'byte string'
str string