Skip to content

Commit

Permalink
Merge pull request #199 from browniebroke/feat/request-xreadlines
Browse files Browse the repository at this point in the history
Add transformer for `request.xreadlines()`
  • Loading branch information
browniebroke authored Sep 16, 2020
2 parents d609166 + 43205a4 commit 26d896c
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 6 deletions.
2 changes: 2 additions & 0 deletions django_codemod/visitors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from .html import UnescapeEntitiesTransformer
from .http import (
HttpRequestXReadLinesTransformer,
HttpUrlQuotePlusTransformer,
HttpUrlQuoteTransformer,
HttpUrlUnQuotePlusTransformer,
Expand Down Expand Up @@ -41,6 +42,7 @@
"FloatRangeFormFieldTransformer",
"FloatRangeModelFieldTransformer",
"ForceTextTransformer",
"HttpRequestXReadLinesTransformer",
"HttpUrlQuotePlusTransformer",
"HttpUrlQuoteTransformer",
"HttpUrlUnQuotePlusTransformer",
Expand Down
37 changes: 35 additions & 2 deletions django_codemod/visitors/http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from django_codemod.constants import DJANGO_3_0, DJANGO_4_0
from django_codemod.visitors.base import BaseFuncRenameTransformer
from libcst import BaseExpression, Call
from libcst import matchers as m

from django_codemod.constants import DJANGO_2_0, DJANGO_3_0, DJANGO_4_0
from django_codemod.visitors.base import (
BaseDjCodemodTransformer,
BaseFuncRenameTransformer,
)


class HttpUrlQuoteTransformer(BaseFuncRenameTransformer):
Expand Down Expand Up @@ -45,3 +51,30 @@ class IsSafeUrlTransformer(BaseFuncRenameTransformer):
removed_in = DJANGO_4_0
rename_from = "django.utils.http.is_safe_url"
rename_to = "django.utils.http.url_has_allowed_host_and_scheme"


class HttpRequestXReadLinesTransformer(BaseDjCodemodTransformer):
"""Replace `HttpRequest.xreadlines()` by iterating over the request."""

deprecated_in = DJANGO_2_0
removed_in = DJANGO_3_0

# This should be conservative and only apply changes to:
# - variables called `request`/`req`
# - `request`/`req` attributes (e.g `self.request`/`view.req`...)
matcher = m.Call(
func=m.Attribute(
value=m.OneOf(
m.Name(value="request"),
m.Name(value="req"),
m.Attribute(attr=m.Name(value="request")),
m.Attribute(attr=m.Name(value="req")),
),
attr=m.Name(value="xreadlines"),
)
)

def leave_Call(self, original_node: Call, updated_node: Call) -> BaseExpression:
if m.matches(updated_node, self.matcher):
return updated_node.func.value
return super().leave_Call(original_node, updated_node)
15 changes: 11 additions & 4 deletions docs/codemods.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,24 @@ Applied by passing the `--removed-in 3.0` or `--deprecated-in 2.0` option:

- Replaces `render_to_response()` by `render()` and add `request=None`
as the first argument of `render()`.
- Replaces `django.utils.lru_cache.lru_cache()` by the function it\'s
- Replaces `django.utils.lru_cache.lru_cache()` by the function it's
an alias of: `functools.lru_cache()`.
- Replaces `django.utils._os.abspathu()` by the function it\'s an
- Replaces `django.utils._os.abspathu()` by the function it's an
alias of: `os.path.abspath()`.
- Replaces `django.utils.encoding.python_2_unicode_compatible()` by
the function it\'s an alias of: `six.python_2_unicode_compatible()`.
the function it's an alias of: `six.python_2_unicode_compatible()`.
- Replaces `django.utils.decorators.ContextDecorator` by the class
from the standard library it\'s an alias to
from the standard library it's an alias to
`contextlib.ContextDecorator`.
- Replace `django.utils.decorators.available_attrs()` by its return
value `functools.WRAPPER_ASSIGNMENTS`.
- Replace `HttpRequest.xreadlines()` by iterating over the request.

This codemodder is quite conservative and will only replace function
calls on variables or attributes called `request` or `req`.

For example, it will fix `request.xreadlines()` or `self.request.xreadlines()`
but not `r.xreadlines()`.

Applied by passing the `--removed-in 3.0` or `--deprecated-in 2.1` option:

Expand Down
2 changes: 2 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def test_deprecated_in_mapping():
"AbsPathTransformer",
"AvailableAttrsTransformer",
"ContextDecoratorTransformer",
"HttpRequestXReadLinesTransformer",
"LRUCacheTransformer",
"RenderToResponseTransformer",
"UnicodeCompatibleTransformer",
Expand Down Expand Up @@ -254,6 +255,7 @@ def test_removed_in_mapping():
"AbsPathTransformer",
"AvailableAttrsTransformer",
"ContextDecoratorTransformer",
"HttpRequestXReadLinesTransformer",
"InlineHasAddPermissionsTransformer",
"LRUCacheTransformer",
"RenderToResponseTransformer",
Expand Down
64 changes: 64 additions & 0 deletions tests/visitors/test_http.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from parameterized import parameterized

from django_codemod.visitors import (
HttpRequestXReadLinesTransformer,
HttpUrlQuotePlusTransformer,
HttpUrlQuoteTransformer,
HttpUrlUnQuotePlusTransformer,
Expand Down Expand Up @@ -96,3 +99,64 @@ def test_simple_substitution(self) -> None:
result = url_has_allowed_host_and_scheme('http://test.com/some-path', None)
"""
self.assertCodemod(before, after)


class TestHttpRequestXReadLinesTransformer(BaseVisitorTest):

transformer = HttpRequestXReadLinesTransformer

def test_noop_wrong_name(self) -> None:
"""Don't replace calls for name other than `request` or `req`."""
before = after = """
for line in r.xreadlines():
print(line)
"""
self.assertCodemod(before, after)

@parameterized.expand(["req", "request"])
def test_simple_substitution(self, variable_name) -> None:
before = f"""
for line in {variable_name}.xreadlines():
print(line)
"""
after = f"""
for line in {variable_name}:
print(line)
"""
self.assertCodemod(before, after)

@parameterized.expand(["req", "request"])
def test_substitution_class_attribute(self, attribute_name) -> None:
before = f"""
for line in self.{attribute_name}.xreadlines():
print(line)
"""
after = f"""
for line in self.{attribute_name}:
print(line)
"""
self.assertCodemod(before, after)

@parameterized.expand(["req", "request"])
def test_substitution_view_attribute(self, attribute_name) -> None:
before = f"""
for line in view.{attribute_name}.xreadlines():
print(line)
"""
after = f"""
for line in view.{attribute_name}:
print(line)
"""
self.assertCodemod(before, after)

@parameterized.expand(["req", "request"])
def test_substitution_nested_attribute(self, attribute_name) -> None:
before = f"""
for line in self.view.{attribute_name}.xreadlines():
print(line)
"""
after = f"""
for line in self.view.{attribute_name}:
print(line)
"""
self.assertCodemod(before, after)

0 comments on commit 26d896c

Please sign in to comment.