Python analyzer updates — July 2021

Over the past month, we have added 1 new Django checker and the ability to Autofix 9 more issues to our Python analyzer. We have also fixed the false-positives you reported for the analyzer.

Here’s a detailed changelog.

New Issues

PTC-W0910 - Missing reverse_code for backward migration

Migration in Django is a way of applying changes that we have made to a model into the database schema.

Sometimes, you might find yourself in a situation when you have to revert or reverse a migration. This is done with the migrate command to go back to a previous migration.

At the moment, this issue will only be raised when the migration operations contains a RunPython command to do things and is missing the logic to go to a previous migration.

By default, this command is not reversible unless its reverse_code argument is provided with a function with the logic to reverse the migration.

Sometimes, a migration (e.g., a data migration) may do nothing when you migrate it forward. So, no schema changes are required when you do a reverse migration. In such cases, instead of implementing an empty function, you can also pass a reference to RunPython.noop, which does nothing.

Example

from django.db import migrations

def update_marks(apps, schema_editor):
    # Logic for a forward migration
    ...

class Migration(migrations.Migration):
    dependencies = [("0002_field_with_default_value", "0001_no_issue")]
    operations = [
        migrations.RunPython(update_marks), # Missing 
    ]

New Autofixes

PTC-W0048 - if statements can be merged

Merges collapsible if statements.

Before Autofix:

if x > 0:
        if y > 0:
            if z > 0:
                doSomething()

After Autofix:

if x > 0 and y > 0 and z > 0:
        doSomething()

PTC-W0062 - with statements can be merged

Merges collapsible with statements.

Before Autofix:

with open("file1", "w") as file1:
    with open("file2", "w") as file2:
        doSomething()

After Autofix:

with open("file1", "w") as file1, open("file2", "w") as file2:
    doSomething()

PYL-E0118 - Name used prior global declaration

Moves global declaration before variable access.

Before Autofix:

var = "Some Value"

def get_result():
    var = "New value"
    global var
    ...

After Autofix:

var = "Some Value"

def get_result():
    global var
    var = "New value"
    ...

PYL-R1708 - StopIteration detected in a generator

Replaces StopIteration with return. See PEP 479 for reference.

Before Autofix:

def some_generator(n):
    for i in range(n):
        yield i
        if i >= 5:
            raise StopIteration

After Autofix:

def some_generator(n):
    for i in range(n):
        yield i
        if i >= 5:
            return

PYL-W0301 - Unnecessary semicolon

Removes unnecessary semicolons.

Before Autofix:

for x in range(10):
    if input[x]:
        i = x - 2;
        x = i * i;
    print(i);

After Autofix:

for x in range(10):
    if input[x]:
        i = x - 2
        x = i * i
    print(i)

PYL-W0706 - Except handler raises immediately

Removes unnecessary except handlers.

Before Autofix:

try:
    run_transaction(transaction_id)
except ValueError as e: 
    raise TransactionError(e)
except: # Issue
    raise

After Autofix:

try:
    run_transaction(transaction_id)
except ValueError as e:
    raise TransactionError(e)

PTC-W0910 - Missing backward migration

Adds RunPython.noop as reverse migration.

Note: This Autofix needs to be verified after the patches are generated. Since this is adding RunPython.noop as the reverse_code at the moment, we recommend you not to use it if the backward migration requires logic for schema changes.

Before Autofix:

from django.db import migrations

def update_marks(apps, schema_editor):
    pass

class Migration(migrations.Migration):
    dependencies = [("0002_field_with_default_value", "0001_no_issue")]
    operations = [
        migrations.RunPython(update_marks),
    ]

After Autofix:

from django.db import migrations

def update_marks(apps, schema_editor):
    pass

class Migration(migrations.Migration):
    dependencies = [("0002_field_with_default_value", "0001_no_issue")]
    operations = [
        migrations.RunPython(update_marks, reverse_code=migrations.RunPython.noop),
    ]

PYL-W0104 - Statement has no effect

Removes the unnecessary statements that have no effect on your code.

Before Autofix:

x = input()
x == 3 # Statement has no effect

calculate_res(x)

After Autofix:

x = input()

calculate_res(x)

PTC-W0047 - Empty block of code found

Removes empty blocks of code that aren’t doing anything.

Before Autofix:

x = 3
for i in range(10):
    pass

...

After Autofix:

x = 3

...

False-positive fixes:

  • PYL-W0621 is no longer raised for pytest fixtures.
  • PTC-W0063 is not raised when the nested function is accessed using the locals method.
  • PYL-W0108 is no longer flagged for methods from factoryboy library.
  • The checker for PTC-W0048 has been modified to account for walrus operator (:=) in the mergeable if conditions.
  • PYL-W0221 is no longer raised for methods in the torchmetric library.
1 Like