Skip to content

Releases: dosisod/refurb

Release v2.0.0

11 Mar 05:39
Compare
Choose a tag to compare

This new release greatly improves the type checking capabilities of Refurb. You might find that you are getting a lot more errors in your projects, as well as a few false positives. Please report any incorrect changes you might find!

Add better type deduction

Refurb now makes better use of Mypy's type deduction system, allowing Refurb to find more errors and with better accuracy.

For example, the following is not detected in older versions of Refurb:

nums = [[]]

if len(nums[0]):
    print("nums[0] is not empty")

With this new version of Refurb, the type of nums[0] is inferred as a list:

$ refurb file.py
file.py:3:4 [FURB115]: Replace `len(nums[0])` with `nums[0]`

While this new type deduction is a great improvement on what existed before, there is still a lot of room for improvement. If you find any typing related bugs, please report it!

Add use-isinstance-bool check (FURB191)

Don't check if a value is True or False using in, use an isinstance() call.

Bad:

if value in {True, False}:
    pass

Good:

if isinstance(value, bool):
    pass

Add use-str-method check (FURB190)

Don't use a lambda function to call a no-arg method on a string, use the name of the string method directly. It is faster, and often times improves readability.

Bad:

def normalize_phone_number(phone_number: str) -> int:
    digits = filter(lambda x: x.isdigit(), phone_number)

    return int("".join(digits))

Good:

def normalize_phone_number(phone_number: str) -> int:
    digits = filter(str.isdigit, phone_number)

    return int("".join(digits))

Add no-subclass-builtin check (FURB189)

Note: this check is disabled by default, see below.

Subclassing dict, list, or str objects can be error prone, use the UserDict, UserList, and UserStr objects from the collections module instead.

Bad:

class CaseInsensitiveDict(dict):
    ...

Good:

from collections import UserDict

class CaseInsensitiveDict(UserDict):
    ...

Note: isinstance() checks for dict, list, and str types will fail when using the corresponding User class. If you need to pass custom dict or list objects to code you don't control, ignore this check. If you do control the code, consider using the following type checks instead:

  • dict -> collections.abc.MutableMapping
  • list -> collections.abc.MutableSequence
  • str -> No such conversion exists

Rename no-del to use-clear (FURB131)

Read dbe3126 for more info. In short, FURB131 will now detect both of the following snippets:

nums = [[1, 2, 3], [4, 5, 6]]

nums[0][:] = []
del nums[0][:]

Running Refurb:

$ refurb file.py
file.py:3:1 [FURB131]: Replace `nums[0][:] = []` with `nums[0].clear()`
file.py:4:1 [FURB131]: Replace `del nums[0][:]` with `nums[0].clear()`

What's Changed

  • Add no-subclass-builtin check by @dosisod in #324
  • Add better detection and error messages for len compatible types in FURB115 by @dosisod in #331

Full Changelog: v1.28.0...v2.0.0

Release v1.28.0

24 Jan 06:36
Compare
Choose a tag to compare

This release adds improved error messages, more suggestions for existing checks, and 2 brand new checks!

Add remove-prefix-or-suffix check (FURB188)

Don't explicitly check a string prefix/suffix if you're only going to remove it, use .removeprefix() or .removesuffix() instead.

Bad:

def strip_txt_extension(filename: str) -> str:
    return filename[:-4] if filename.endswith(".txt") else filename

Good:

def strip_txt_extension(filename: str) -> str:
    return filename.removesuffix(".txt")

Add use-reverse check (FURB187)

Don't use x[::-1] or reversed(x) to reverse a list and reassign it to itself, use the faster in-place .reverse() method instead.

Bad:

names = ["Bob", "Alice", "Charlie"]

names = reversed(names)
# or
names = list(reversed(names))
# or
names = names[::-1]

Good:

names = ["Bob", "Alice", "Charlie"]

names.reverse()

Detect itemgetter() in FURB118 (use-operator)

The operator.itemgetter() function can be used to get one or more items from an object, removing the need to create a lambda just to extract values from an object:

Bad:

row = (1, "Some text", True)

transform = lambda x: (x[2], x[0])

Good:

from operator import itemgetter

row = (1, "Some text", True)

transform = itemgetter(2, 0)

Detect no-arg lambdas returning default literals in FURB111 (use-func-name)

Don't use lambdas when you want a default value for a literal type:

Bad:

counter = defaultdict(lambda: 0)
multimap = defaultdict(lambda: [])

Good:

counter = defaultdict(int)
multimap = defaultdict(list)

Full Changelog: v1.27.0...v1.28.0

Release v1.27.0

08 Jan 08:13
Compare
Choose a tag to compare

This release adds 2 new checks and adds improved detection and error messages for a few other checks!

Add no-copy-with-merge Check (FURB185)

You don't need to call .copy() on a dict/set when using it in a union since the original dict/set is not modified.

Bad:

d = {"a": 1}

merged = d.copy() | {"b": 2}

Good:

d = {"a": 1}

merged = d | {"b": 2}

Add use-sort Check (FURB186)

Don't use sorted() to sort a list and reassign it to itself, use the faster in-place .sort() method instead.

Bad:

names = ["Bob", "Alice", "Charlie"]

names = sorted(names)

Good:

names = ["Bob", "Alice", "Charlie"]

names.sort()

Color is now disabled when piping output

Previously Refurb would always emit color unless you turned it off. Now it will check if the output is a TTY, and if so, disable the color.

Improve use-chain-from-iterable (FURB179)

FURB179 now detects the following snippets:

functools.reduce(operator.add, rows)
functools.reduce(operator.add, rows, [])
functools.reduce(operator.concat, rows)
functools.reduce(operator.concat, rows, [])

These snippets are slow because they use the add/concat operator. This will create a new list for each iteration instead of doing it in-place.

Improve use-dict-union (FURB173)

FURB173 now detects the following snippets:

_ = dict(**x)
_ = dict(x, **y)
_ = dict(**x, **y)
_ = dict(x, a=1)
_ = dict(**x, a=1, b=2)
_ = dict(**x, **y, a=1, b=2)

These can be re-written using dictionary unions:

_ = {**x}
_ = x | y
_ = x | y
_ = x | {"a": 1}
_ = x | {"a": 1, "b": 2}
_ = x | y | {"a": 1, "b": 2}

Release v1.26.0

23 Dec 18:54
Compare
Choose a tag to compare

This release adds one new check and a bunch of bug fixes!

Add use-fluent-interface (FURB184)

When an API has a Fluent Interface (the ability to chain multiple calls together), you should chain those calls instead of repeatedly assigning and using the value. Sometimes a return statement can be written more succinctly:

Bad:

def get_tensors(device: str) -> torch.Tensor:
    t1 = torch.ones(2, 1)
    t2 = t1.long()
    t3 = t2.to(device)
    return t3

def process(file_name: str):
    common_columns = ["col1_renamed", "col2_renamed", "custom_col"]
    df = spark.read.parquet(file_name)
    df = df \
        .withColumnRenamed('col1', 'col1_renamed') \
        .withColumnRenamed('col2', 'col2_renamed')
    df = df \
        .select(common_columns) \
        .withColumn('service_type', F.lit('green'))
    return df

Good:

def get_tensors(device: str) -> torch.Tensor:
    t3 = (
        torch.ones(2, 1)
        .long()
        .to(device)
    )
    return t3

def process(file_name: str):
    common_columns = ["col1_renamed", "col2_renamed", "custom_col"]
    df = (
        spark.read.parquet(file_name)
        .withColumnRenamed('col1', 'col1_renamed')
        .withColumnRenamed('col2', 'col2_renamed')
        .select(common_columns)
        .withColumn('service_type', F.lit('green'))
    )
    return df

Thank you to @sbrugman for implementing this new check!

What's Changed

  • Fix FURB183 false positive when using custom formatter by @dosisod in #315
  • Fix FURB118 operator.contains false positive, improve error messages by @dosisod in #317
  • Initial implementation for fluid interface check by @sbrugman in #287
  • Fix: false negative in FURB111 by @sbrugman in #313
  • Fix FURB115 being emitted when using len(x) with non-boolean operators by @dosisod in #319

New Contributors

Full Changelog: v1.25.0...v1.26.0

Release v1.25.0

06 Dec 00:42
435963c
Compare
Choose a tag to compare

This release adds colorized output to Refurb, as well as some bugfixes!

image

Color output is enabled by default. If you don't like it you can disable it using any of the following methods:

  • Use the --no-color CLI arg
  • Use color = false in the config file
  • Set the NO_COLOR env var

What's Changed

  • Don't emit FURB135/FURB148 when using _ in comprehensions by @dosisod in #312

Full Changelog: v1.24.0...v1.25.0

Release v1.24.0

17 Nov 02:55
Compare
Choose a tag to compare

This release adds 2 new checks, some documentation updates, and allows for using Refurb with the new 1.7.0 release of Mypy. Thank you @bzoracler for fixing this!

Unpin Mypy v1.7.0

Mypy v1.7.0 made an internal change that broke Refurb (see #305). A workaround has since been found meaning we no longer need to pin to Mypy <1.7.0. If you are still experiencing any issues with using older/newer versions of Mypy with Refurb, please open an issue!

Add use-hexdigest-hashlib check (FURB181)

Use .hexdigest() to get a hex digest from a hash.

Bad:

from hashlib import sha512

hashed = sha512(b"some data").digest().hex()

Good:

from hashlib import sha512

hashed = sha512(b"some data").hexdigest()

Add simplify-hashlib-ctor (FURB182)

You can pass data into hashlib constructors, so instead of creating a hash object and immediately updating it, pass the data directly.

Bad:

from hashlib import sha512

h = sha512()
h.update(b"data)

Good:

from hashlib import sha512

h = sha512(b"data")

What's Changed

New Contributors

Full Changelog: v1.23.0...v1.24.0

Release v1.23.0

13 Nov 21:12
63209fc
Compare
Choose a tag to compare

This is a minor patch that pins Mypy to version <1.7.0, see #305 for more info.

What's Changed

Full Changelog: v1.22.2...v1.23.0

Release v1.22.2

10 Nov 23:52
b5f9f11
Compare
Choose a tag to compare

This is a bug fix release that fixes crashes on certain files.

What's Changed

Full Changelog: v1.22.1...v1.22.2

v1.22.1

21 Oct 03:16
229e7ea
Compare
Choose a tag to compare

This is a minor patch that fixes some typos and improves the accuracy of emitted error messages for FURB140 and FURB179.

What's Changed

Full Changelog: v1.22.0...v1.22.1

v1.22.0

17 Oct 19:07
6db8eb5
Compare
Choose a tag to compare

This version adds 2 new checks, some bug fixes, and documentation fixes.

Improved Error Messages

Previously Refurb would assume certain identifiers such as Path where imported as Path, not pathlib.Path. This made the context of error messages less helpful since they did not reflect what was actually in the source. Now more checks will use the more accurate pathlib.Path identifier if needed.

Add use-chain-from-iterable check (FURB179)

When flattening a list of lists, use the chain.from_iterable function from the itertools stdlib package. This function is faster than native list/generator comprehensions or using sum() with a list default.

Bad:

from itertools import chain

rows = [[1, 2], [3, 4]]

# using list comprehension
flat = [col for row in rows for col in row]

# using sum()
flat = sum(rows, [])

# using chain(*x)
flat = chain(*rows)

Good:

from itertools import chain

rows = [[1, 2], [3, 4]]

flat = chain.from_iterable(rows)

Note: chain(*x) may be marginally faster/slower depending on the length of x. Since * might potentially expand to a lot of arguments, it is better to use chain.from_iterable() when you are unsure.

Add use-abc-shorthand check (FURB180)

Instead of setting metaclass directly, inherit from the ABC wrapper class. This is semantically the same thing, but more succinct.

Bad:

class C(metaclass=ABCMeta):
    pass

Good:

class C(ABC):
    pass

What's Changed

Full Changelog: v1.21.0...v1.22.0