ruff G010
labelled unknown
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
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r2-p005 | fix-changes-behavior | labelled safe | real · medium | duplicate | The 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-p001 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule rewrites the deprecated .warn() method to .warning() as documented. The stderr difference occurs because the DeprecationWarning emitted by the old cod… |
| r1-p002 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule replaces the deprecated `logger.warn` method with `logger.warning` as documented. The stderr difference occurs because the DeprecationWarning emitted … |
| r1-p007 | fix-changes-behavior | labelled safe | intended-change · low | no match | The rule intentionally rewrites the deprecated `logger.warn` method to `logger.warning` to eliminate the DeprecationWarning, which is the documented purpose of… |
| r2-p003 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule rewrites the deprecated `logger.warn` call to `logger.warning`, which the documentation states is functionally equivalent. The observed difference is … |
| r2-p004 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule replaces the deprecated `logger.warn` with `logger.warning`, which the documentation states are functionally equivalent. The observed difference is th… |
| r2-p007 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule rewrites the deprecated `warn` method to `warning` to eliminate the DeprecationWarning, which is exactly the documented purpose. The stderr output cha… |
| r2-p008 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule rewrites the deprecated `logger.warn` call to `logger.warning` as documented. The stderr difference is exactly the removal of the DeprecationWarning t… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix-changes-behavior | Multiple logger-like objects - heuristic misapplication | 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.logg |
| r1-p002 | fix-changes-behavior | Exceptions inside rewritten code with finally/else/chaining | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p003 | silent | Dynamic getattr lookup - rewrite misses attribute access | If `warn` is accessed via `getattr(obj, 'warn')` dynamically, the textual replacement does not affect the attribute lookup and may leave stale code. |
| r1-p004 | fix ok | Bound method stored earlier - stored reference not updated | If `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-p005 | fix ok | Local variable named `warn` in same scope | 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 acce |
| r1-p006 | fix ok | Dict ordering, duplicate keys, __hash__/__eq__, sets | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p007 | fix-changes-behavior | `warn` as substring in larger identifier | If `warn` is part of a larger identifier like `warning_prefix`, the replacement would produce `warning_prefixing`, causing a NameError or unintended variable reference. |
| r1-p008 | fix ok | File modes, truncation, partial writes, non-str data | file modes and truncation, partial writes, non-str data passed to write |
| r2-p001 | silent prediction off | Custom warn method differs from warning | Rewriting obj.warn to obj.warning changes behavior if obj defines a custom .warn method distinct from .warning |
| r2-p002 | silent | warn inside string literal not rewritten | 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 |
| r2-p003 | fix-changes-behavior | String formatting edge cases with % and f-strings | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p004 | fix-changes-behavior | Generator consumed once, logger.warn inside generator expression | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p005 | fix-changes-behavior | Conditionally assigned logger missing .warning in fallback | 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 |
| r2-p006 | silent | Dynamic getattr access to warn not affected by textual replace | If warn is accessed via getattr(obj, 'warn') dynamically, the textual replacement does not affect the attribute lookup and may leave stale code. |
| r2-p007 | fix-changes-behavior | Aliased logger import not recognized by lint.logger-objects | 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. |
| r2-p008 | fix-changes-behavior | Bytes vs str in warn argument encoding handling | bytes 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