Skip to content

Releases: dosisod/refurb

Version 1.21.0

08 Sep 23:02
68a878e
Compare
Choose a tag to compare

This version adds some small bug fixes and improvements to certain checks.

What's Changed

Full Changelog: v1.20.0...v1.21.0

Version 1.20.0

15 Aug 21:13
Compare
Choose a tag to compare

This is a small release that should address some of the slowness in Refurb. More speedups coming soon!

Speedup Refurb

There was a small bug in Refurb where it would reload all the checks for each file when it only needed to do it once at the beginning. After fixing this, running Refurb against itself is about 1 second (~10%) faster now. Your mileage my vary, but codebases with more files will benefit more from this fix.

Add --timing-stats flag

This flag will dump a bunch of timing information to a JSON file for diagnosing speed issues. Read the docs for more info.

What's Changed

Full Changelog: v1.19.0...v1.20.0

Version 1.19.0

05 Aug 21:54
Compare
Choose a tag to compare

This release includes 1 new check, fixes typos in the documentation, and adds more utility to the --verbose flag.

Add use-shlex-join check (FURB178)

When using shlex to escape and join a bunch of strings consider using the shlex.join method instead.

Bad:

args = ["hello", "world!"]

cmd = " ".join(shlex.quote(arg) for arg in args)

Good:

args = ["hello", "world!"]

cmd = shlex.join(args)

Add --verbose support to --explain

When the --verbose flag is used with the --explain flag the filename for the check being explained will be displayed.

For example:

$ refurb --explain FURB123 --verbose
Filename: refurb/checks/readability/no_unnecessary_cast.py

FURB123: no-redundant-cast [readability]

...

This will help developers find the source files for a given check much quicker then before.

What's Changed

New Contributors

Full Changelog: v1.18.0...v1.19.0

Version 1.18.0

29 Jul 03:59
Compare
Choose a tag to compare

This release adds 4 new checks, a new flag, and a few bug fixes.

Add simplify-fastapi-query check (FURB175)

FastAPI will automatically pass along query parameters to your function, so you only need to use Query() when you use params other than default.

Bad:

@app.get("/")
def index(name: str = Query()) -> str:
    return f"Your name is {name}"

Good:

@app.get("/")
def index(name: str) -> str:
    return f"Your name is {name}"

Add unreliable-utc-usage check (FURB176)

Because naive datetime objects are treated by many datetime methods as local times, it is preferred to use aware datetimes to represent times in UTC.

This check affects datetime.utcnow and datetime.utcfromtimestamp.

Bad:

from datetime import datetime

now = datetime.utcnow()
past_date = datetime.utcfromtimestamp(some_timestamp)

Good:

from datetime import datetime, timezone

datetime.now(timezone.utc)
datetime.fromtimestamp(some_timestamp, tz=timezone.utc)

Add no-implicit-cwd check (FURB177)

If you want to get the current working directory don't call resolve() on an empty Path() object, use Path.cwd() instead.

Bad:

cwd = Path().resolve()

Good:

cwd = Path.cwd()

Add --verbose flag

This flag will spit out extra information about Refurb and related configuration info. Currently the --verbose flag will only print the enabled checks, though more information might be displayed in the future.


What's Changed

New Contributors

Full Changelog: v1.17.0...v1.18.0

Version 1.17.0

26 Jun 05:57
Compare
Choose a tag to compare

This release adds 3 new checks and some general bug fixes.

Add use-suffix check (FURB172)

Note: This check is disabled by default.

When checking the file extension for a pathlib object don't call endswith() on the name field, directly check against suffix instead.

Bad:

from pathlib import Path

def is_markdown_file(file: Path) -> bool:
    return file.name.endswith(".md")

Good:

from pathlib import Path

def is_markdown_file(file: Path) -> bool:
    return file.suffix == ".md"

Add use-dict-union check (FURB173)

Dicts can be created/combined in many ways, one of which is the ** operator (inside the dict), and another is the | operator (used outside the dict). While they both have valid uses, the | operator allows for more flexibility, including using |= to update an existing dict.

See PEP 584 for more info.

Bad:

def add_defaults(settings: dict[str, str]) -> dict[str, str]:
    return {"color": "1", **settings}

Good:

def add_defaults(settings: dict[str, str]) -> dict[str, str]:
    return {"color": "1"} | settings

Add simplify-token-function check (FURB174)

Depending on how you are using the secrets module there might be a more expressive ways of writing what it is you're trying to write.

Bad:

random_hex = token_bytes().hex()
random_url = token_urlsafe()[:16]

Good:

random_hex = token_hex()
random_url = token_urlsafe(16)

Bugs

  • Fix tuple related issue in Mypy 1.4.0 where certain checks would be ignored
  • Ensure config file exists and is not a directory
  • Disallow empty strings as arguments

Version 1.16.0

25 Apr 04:11
Compare
Choose a tag to compare

This version adds a few new checks (and fixes a few bugs).

Add use-long-regex-flag check (FURB170)

Regex operations can be changed using flags such as re.I, which will make the regex case-insensitive. These single-character flag names can be harder to read/remember, and should be replaced with the longer aliases so that they are more descriptive.

Bad:

if re.match("^hello", "hello world", re.I):
    pass

Good:

if re.match("^hello", "hello world", re.IGNORECASE):
    pass

Add no-single-item-in check (FURB171)

Don't use in to check against a single value, use == instead:

Bad:

if name in ("bob",):
    pass

Good:

if name == "bob":
    pass

What's Changed

Full Changelog: v1.15.0...v1.16.0

Version 1.15.0

24 Mar 06:17
2c336ac
Compare
Choose a tag to compare

This version adds 3 new checks!

Add use-long-regex-flag check (FURB167)

Regex operations can be changed using flags such as re.I, which will make the regex case-insensitive. These single-character flag names can be harder to read/remember, and should be replaced with the longer aliases so that they are more descriptive.

Bad:

if re.match("^hello", "hello world", re.I):
    pass

Good:

if re.match("^hello", "hello world", re.IGNORECASE):
    pass

Add no-isinstance-type-none (FURB168)

Checking if an object is None using isinstance() is un-pythonic: use an is comparison instead.

Bad:

x = 123

if isinstance(x, type(None)):
    pass

Good:

x = 123

if x is None:
    pass

Add no-is-type-none check (FURB169)

Don't use type(None) to check if the type of an object is None, use an is comparison instead.

Bad:

x = 123

if type(x) is type(None):
    pass

Good:

x = 123

if x is None:
    pass

What's Changed

Full Changelog: v1.14.0...v1.15.0

Version 1.14.0

14 Mar 04:05
70dd579
Compare
Choose a tag to compare

This PR includes a few bug fixes, a few new features, and 4 new checks!

Bug Fixes

  • Error codes can be comma separated in noqa comments now
  • Unused fields or fields with the wrong type will throw an error now

New Features

  • Add ability to output GitHub Annotations using --format github
  • Add ability to sort error output by error code via --sort error

Add simplify-math-log check (FURB163)

Use the shorthand log2 and log10 functions instead of passing 2 or 10 as the second argument to the log function. If math.e is used as the second argument, just use math.log(x) instead, since e is the default.

Bad:

power = math.log(x, 10)

Good:

power = math.log10(x)

Add no-from-float check (FURB164)

When constructing a Fraction or Decimal using a float, don't use the from_float() or from_decimal() class methods: Just use the more consice Fraction() and Decimal() class constructors instead.

Bad:

ratio = Fraction.from_float(1.2)
score = Decimal.from_float(98.0)

Good:

ratio = Fraction(1.2)
score = Decimal(98.0)

Add no-temp-class-object check (FURB165)

You don't need to construct a class object to call a static method or a class method, just invoke the method on the class directly:

Bad:

cwd = Path().cwd()

Good:

cwd = Path.cwd()

Add use-int-base-zero check (FURB166)

When converting a string starting with 0b, 0o, or 0x to an int, you don't need to slice the string and set the base yourself: just call int() with a base of zero. Doing this will autodeduce the correct base to use based on the string prefix.

Bad:

num = "0xABC"

if num.startswith("0b"):
    i = int(num[2:], 2)
elif num.startswith("0o"):
    i = int(num[2:], 8)
elif num.startswith("0x"):
    i = int(num[2:], 16)

print(i)

Good:

num = "0xABC"

i = int(num, 0)

print(i)

This check is disabled by default because there is no way for Refurb to detect whether the prefixes that are being stripped are valid Python int prefixes (like 0x) or some other prefix which would fail if parsed using this method.

What's Changed

Full Changelog: v1.13.0...v1.14.0

Version 1.13.0

23 Feb 08:08
4e87722
Compare
Choose a tag to compare

This check includes a few bug fixes, as well as many new checks!

The --python-version setting defaults to currently installed version now

Previously Refurb would be completely unaware of what version of Python you where checking unless you set the --python-version 3.x flag. Now, unless you override it with that flag, Refurb will default to whatever the currently installed version of Python on your machine is.

Add simplify-strip check (FURB159)

In some situations the .lstrip(), .rstrip() and .strip() string methods can be written more succinctly: strip() is the same thing as calling both lstrip() and rstrip() together, and all the strip functions take an iterable argument of the characters to strip, meaning you don't need to call a strip function multiple times with different arguments, you can just concatenate them and call it once.

Bad:

name = input().lstrip().rstrip()

num = "  -123".lstrip(" ").lstrip("-")

Good:

name = input().strip()

num = "  -123".lstrip(" -")

Add no-redundant-assignment check (FURB160)

Sometimes when you are debugging (or copy-pasting code) you will end up with a variable that is assigning itself to itself. These lines can be removed.

Bad:

name = input("What is your name? ")
name = name

Good:

name = input("What is your name? ")

Add use-bit-count check (FURB161)

Python 3.10 adds a very helpful bit_count() function for integers which counts the number of set bits. This new function is more descriptive and faster compared to converting/counting characters in a string.

Bad:

x = bin(0b1010).count("1")

assert x == 2

Good:

x = 0b1010.bit_count()

assert x == 2

Add simplify-fromisoformat check (FURB162)

Python 3.11 adds support for parsing UTC timestamps that end with Z, thus removing the need to strip and append the +00:00 timezone.

Bad:

date = "2023-02-21T02:23:15Z"

start_date = datetime.fromisoformat(date.replace("Z", "+00:00"))

Good:

date = "2023-02-21T02:23:15Z"

start_date = datetime.fromisoformat(date)

What's Changed

Full Changelog: v1.12.0...v1.13.0

Version 1.12.0

09 Feb 07:22
Compare
Choose a tag to compare

Important Mypy Update Info

This is the 3rd release in the past 3 days, sorry for the spam! This version is pretty important though, since it removes the version cap on Mypy: They just released Mypy version 1.0 a few days ago, the same day that v1.11.1 (the lastest version) of Refurb came out. Since a lot of people will be upgrading Mypy in the coming days, it is important to remove this cap so that people don't get version conflicts. Now that Mypy has reached 1.0, a "stable" release, there should be less of a concern when it comes to spontaneous breakage.

With that in mind, this newest version does include one new check!

Add simplify-as-pattern-with-builtin check (FURB158)

This check looks for instances where builtin types such as int(), float(), etc. are used in as patterns (such as int() as x). Although this is totally valid, it can be more succinctly written as int(x).

Here is what that looks like side-by-side:

match data:
    case str() as name:
        print(f"Hello {name}")

Compare that to the simpler version:

match data:
    case str(name):
        print(f"Hello {name}")

Full Changelog: v1.11.1...v1.12.0