Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

codemod to deprecate logging.warn #206

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions integration_tests/test_fix_deprecated_logging_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from core_codemods.fix_deprecated_logging_warn import FixDeprecatedLoggingWarn
from integration_tests.base_test import (
BaseIntegrationTest,
original_and_expected_from_code_path,
)


class TestFixDeprecatedLoggingWarn(BaseIntegrationTest):
codemod = FixDeprecatedLoggingWarn
code_path = "tests/samples/fix_deprecated_logging_warn.py"
original_code, expected_new_code = original_and_expected_from_code_path(
code_path, [(3, 'log.warning("hello")\n')]
)
expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n import logging\n \n log = logging.getLogger("my logger")\n-log.warn("hello")\n+log.warning("hello")\n'
expected_line_change = "4"
change_description = FixDeprecatedLoggingWarn.CHANGE_DESCRIPTION
4 changes: 4 additions & 0 deletions src/codemodder/scripts/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ class DocMetadata:
importance="Low",
guidance_explained="Simplifying expressions involving `startswith` or `endswith` calls is safe.",
),
"fix-deprecated-logging-warn": DocMetadata(
importance="Low",
guidance_explained="This change fixes deprecated uses and is safe.",
),
}


Expand Down
2 changes: 2 additions & 0 deletions src/core_codemods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from .remove_module_global import RemoveModuleGlobal
from .remove_debug_breakpoint import RemoveDebugBreakpoint
from .combine_startswith_endswith import CombineStartswithEndswith
from .fix_deprecated_logging_warn import FixDeprecatedLoggingWarn

registry = CodemodCollection(
origin="pixee",
Expand Down Expand Up @@ -92,5 +93,6 @@
RemoveModuleGlobal,
RemoveDebugBreakpoint,
CombineStartswithEndswith,
FixDeprecatedLoggingWarn,
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
The `warn` method from `logging` has been [deprecated](https://docs.python.org/3/library/logging.html#logging.Logger.warning) in favor of `warning` since Python 3.3. Since the old method `warn` has been retained for a long time, there are a lot of developers that are unaware of this change and consequently a lot of code using the older method.

Our changes look like the following:
```diff
import logging

- logging.warn("hello")
+ logging.warning("hello")
...
log = logging.getLogger("my logger")
- log.warn("hello")
+ log.warning("hello")
```
55 changes: 55 additions & 0 deletions src/core_codemods/fix_deprecated_logging_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import libcst as cst
from codemodder.codemods.api import SemgrepCodemod, ReviewGuidance
from codemodder.codemods.utils_mixin import NameResolutionMixin


class FixDeprecatedLoggingWarn(SemgrepCodemod, NameResolutionMixin):
NAME = "fix-deprecated-logging-warn"
SUMMARY = "Replace Deprecated `logging.warn`"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
DESCRIPTION = "Replace deprecated `logging.warn` with `logging.warning`"
REFERENCES = [
{
"url": "https://docs.python.org/3/library/logging.html#logging.Logger.warning",
"description": "",
},
]
_module_name = "logging"

@classmethod
def rule(cls):
return """
rules:
- pattern-either:
- patterns:
- pattern: logging.warn(...)
- pattern-inside: |
import logging
...
- patterns:
- pattern: logging.getLogger(...).warn(...)
- pattern-inside: |
import logging
...
- patterns:
- pattern: $VAR.warn(...)
- pattern-inside: |
import logging
...
$VAR = logging.getLogger(...)
...

"""

def on_result_found(self, original_node, updated_node):
warning = cst.Name(value="warning")
match original_node.func:
case cst.Name():
self.add_needed_import(self._module_name, "warning")
self.remove_unused_import(original_node.func)
return updated_node.with_changes(func=warning)
case cst.Attribute():
return updated_node.with_changes(
func=updated_node.func.with_changes(attr=warning)
)
return original_node

Check warning on line 55 in src/core_codemods/fix_deprecated_logging_warn.py

View check run for this annotation

Codecov / codecov/patch

src/core_codemods/fix_deprecated_logging_warn.py#L55

Added line #L55 was not covered by tests
109 changes: 109 additions & 0 deletions tests/codemods/test_fix_deprecated_logging_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import pytest
from core_codemods.fix_deprecated_logging_warn import FixDeprecatedLoggingWarn
from tests.codemods.base_codemod_test import BaseSemgrepCodemodTest


class TestFixDeprecatedLoggingWarn(BaseSemgrepCodemodTest):
clavedeluna marked this conversation as resolved.
Show resolved Hide resolved
codemod = FixDeprecatedLoggingWarn

@pytest.mark.parametrize(
"code",
[
"""
import logging
logging.{}('something')
""",
"""
import logging
logging.{}("something %s and %s", "foo", "bar")
""",
"""
import logging
log = logging.getLogger('anything')
log.{}('something')
""",
],
)
def test_import(self, tmpdir, code):
original_code = code.format("warn")
new_code = code.format("warning")
self.run_and_assert(tmpdir, original_code, new_code)
assert len(self.file_context.codemod_changes) == 1

@pytest.mark.parametrize(
"code",
[
"""
from logging import {0}
{0}('something')
""",
"""
from logging import getLogger
getLogger('anything').{0}('something')
""",
],
)
def test_from_import(self, tmpdir, code):
original_code = code.format("warn")
new_code = code.format("warning")
self.run_and_assert(tmpdir, original_code, new_code)
assert len(self.file_context.codemod_changes) == 1

@pytest.mark.parametrize(
"input_code,expected_output",
[
(
"""\
from logging import warn as warn_func
warn_func('something')""",
"""\
from logging import warning
warning('something')""",
),
(
"""\
from logging import getLogger as make_logger
logger = make_logger('anything')
logger.warn('something')""",
"""\
from logging import getLogger as make_logger
logger = make_logger('anything')
logger.warning('something')""",
),
],
)
def test_import_alias(self, tmpdir, input_code, expected_output):
self.run_and_assert(tmpdir, input_code, expected_output)
assert len(self.file_context.codemod_changes) == 1

@pytest.mark.parametrize(
"code",
[
"""
import xyz
xyz.warn('something')
""",
"""
import my_logging
log = my_logging.getLogger('anything')
log.warn('something')
""",
],
)
def test_different_warn(self, tmpdir, code):
self.run_and_assert(tmpdir, code, code)
assert len(self.file_context.codemod_changes) == 0

@pytest.mark.xfail(reason="Not currently supported")
def test_log_as_arg(self, tmpdir):
code = """\
import logging
log = logging.getLogger('foo')
def some_function(logger):
logger.{}("hi")
some_function(log)
"""
original_code = code.format("warn")
new_code = code.format("warning")
self.run_and_assert(tmpdir, original_code, new_code)
assert len(self.file_context.codemod_changes) == 1
4 changes: 4 additions & 0 deletions tests/samples/fix_deprecated_logging_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import logging

log = logging.getLogger("my logger")
log.warn("hello")
Loading