Releases: dosisod/refurb
Version 1.21.0
This version adds some small bug fixes and improvements to certain checks.
What's Changed
- Add example config files by @dosisod in #280
- Fix tests failing in Python 3.12 by @dosisod in #281
- Detect
not
when used withstartswith
andendswith
by @dosisod in #282 - Only allow built-in mapping types in FURB173 by @dosisod in #284
- Improve FURB111, bump version by @dosisod in #285
Full Changelog: v1.20.0...v1.21.0
Version 1.20.0
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
- fix(no_unnecessary_cast): fix typo in shallow by @ChaoticRoman in #278
Full Changelog: v1.19.0...v1.20.0
Version 1.19.0
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
- Improved docs by @trag1c in #273
- Add
use-shlex-join
check by @dosisod in #275 - Fix typos by @kianmeng in #274
- Made pipx the recommended installation method by @trag1c in #277
New Contributors
Full Changelog: v1.18.0...v1.19.0
Version 1.18.0
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
- Add
simplify-fastapi-query
check by @dosisod in #256 - Warn
datetime.{utcnow,utcfromtimestamp}
usages by @sobolevn in #259 - Do not run
isort
ontest/data
folder by @sobolevn in #264 - Add title and improve one fix by @ChaoticRoman in #265
- Improve coverage by @dosisod in #267
- Add
--verbose
flag, update documentation by @dosisod in #271 - Make
--verbose
output on a single line by @dosisod in #272 - Use
pip install -e .
inMakefile
by @sobolevn in #260
New Contributors
- @sobolevn made their first contribution in #259
- @ChaoticRoman made their first contribution in #265
Full Changelog: v1.17.0...v1.18.0
Version 1.17.0
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
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
- Add
use-regex-pattern-methods
check by @dosisod in #244 - Bump packages by @dosisod in #245
- Enable more
ruff
checks by @dosisod in #247 - Fix settings from config file getting overridden by command line args by @dosisod in #249
- Add
no-single-item-in
check by @dosisod in #250
Full Changelog: v1.15.0...v1.16.0
Version 1.15.0
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
- Allow for extracting line/column info from nodes by @dosisod in #233
- Bump packages, cleanup
pyproject.toml
by @dosisod in #235 - Add
use-long-regex-flag
check by @dosisod in #236 - Remove
pre-commit
shell script, cleanup coverage output by @dosisod in #237 - Remove part of FURB131: by @dosisod in #239
- Add
rstrip
andstrip
support to FURB139 by @dosisod in #240 - Add
no-isinstance-type-none
check by @dosisod in #241 - Add
no-is-type-none
check by @dosisod in #242 - Bump version by @dosisod in #243
Full Changelog: v1.14.0...v1.15.0
Version 1.14.0
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
- Add
simplify-math-log
check by @dosisod in #213 - Allow for multiple error codes in
noqa
comments by @dosisod in #218 - Error when unknown field is found in config file by @dosisod in #219
- Add type checking for fields in config file by @dosisod in #220
- Bump versions by @dosisod in #223
- Add
no-from-float
check by @dosisod in #224 - Add better type inference by @dosisod in #225
- Add output format for GitHub Annotations by @dosisod in #226
- Add
no-temp-class-object
check by @dosisod in #227 - Add
use-int-base-zero
check by @dosisod in #228 - Add
--sort
flag by @dosisod in #229 - Autogenerate documentation for checks by @dosisod in #230
- Bump packages by @dosisod in #231
- Release v1.14.0 by @dosisod in #232
Full Changelog: v1.13.0...v1.14.0
Version 1.13.0
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
- Add
simplify-strip
check by @dosisod in #202 - Add more flake8 plugins by @dosisod in #204
- Add
no-redundant-assignment
check by @dosisod in #205 - Add
use-bit-count
check by @dosisod in #208 - Emit error for empty string in
Path()
constructor by @dosisod in #209 - Add
simplify-fromisoformat
check by @dosisod in #210 - Set
python_version
to currently installed Python version by @dosisod in #211 - Replace
flake8
withruff
, bump version by @dosisod in #212
Full Changelog: v1.12.0...v1.13.0
Version 1.12.0
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