Package: refurb
RefurbA tool for refurbishing and modernizing Python codebases.Example# main.pyforfilenamein["file1.txt","file2.txt"]:withopen(filename)asf:contents=f.read()lines=contents.splitlines()forlineinlines:ifnotlineorline.startswith("# ")orline.startswith("// "):continueforwordinline.split():print(f"[{word}]",end="")print("")Running:$ refurb main.py
main.py:3:17 [FURB109]: Use `in (x, y, z)` instead of `in [x, y, z]`
main.py:4:5 [FURB101]: Use `y = Path(x).read_text()` instead of `with open(x, ...) as f: y = f.read()`
main.py:10:40 [FURB102]: Replace `x.startswith(y) or x.startswith(z)` with `x.startswith((y, z))`
main.py:16:9 [FURB105]: Use `print() instead of `print("")`Installing$ pipx install refurb
$ refurb file.py folder/NoteRefurb must be run on Python 3.10+, though it can check Python 3.7+ code by setting the--python-versionflag.Explanations For ChecksYou can userefurb --explain FURB123, whereFURB123is the error code you are trying to look up.
For example:$ refurb --explain FURB123
Don't cast a variable or literal if it is already of that type. For
example:

Bad:

```
name = str("bob")
num = int(123)
```

Good:

```
name = "bob"
num = 123
```An online list of all available checks can be viewedhere.Ignoring ErrorsUse--ignore 123to ignore error 123. The error code can be in the formFURB123or123.
This flag can be repeated.TheFURBprefix indicates that this is a built-in error. TheFURBprefix is optional,
but for all other errors (ie,ABC123), the prefix is required.You can also use inline comments to disable errors:x=int(0)# noqa: FURB123y=list()# noqaHere,noqa: FURB123specifically ignores the FURB123 error for that line, andnoqaignores
all errors on that line.You can also specify multiple errors to ignore by separating them with a comma/space:x=notnotint(0)# noqa: FURB114, FURB123x=notnotint(0)# noqa: FURB114 FURB123Enabling/Disabling ChecksCertain checks are disabled by default, and need to be enabled first. You can do this using the--enable ERRflag, whereERRis the error code of the check you want to enable. A disabled
check differs from an ignored check in that a disabled check will never be loaded, whereas an
ignored check will be loaded, an error will be emitted, and the error will be suppressed.Use the--verbose/-vflag to get a full list of enabled checks.The opposite of--enableis--disable, which will disable a check. When--enableand--disableare both specified via the command line, whichever one comes last will take precedence. When usingenableanddisablevia the config file,disablewill always take precedence.Use the--disable-allflag to disable all checks. This allows you to incrementally--enablechecks
as you see fit, as opposed to adding a bunch of--ignoreflags. To use this in the config file,
setdisable_alltotrue.Use the--enable-allflag to enable all checks by default. This allows you to opt into all checks
that Refurb (and Refurb plugins) have to offer. This is a good option for new codebases. To use this
in a config file, setenable_alltotrue.In the config file,disable_all/enable_allis applied first, and then theenableanddisablefields are applied afterwards.Note thatdisable_allandenable_allare mutually exclusive, both on the command line and in
the config file. You will get an error if you try to specify both.You can also disable checks by category using the#categorysyntax. For example,--disable "#readability"will disable all checks with thereadabilitycategory. The same applies forenableandignore.
Also, if you disable an entire category you can still explicitly re-enable a check in that category.Note that#readabilityis wrapped in quotes because your shell will interpret the#as the
start of a comment.Setting Python VersionUse the--python-versionflag to tell Refurb which version of Python your codebase is using. This
should allow for better detection of language features, and allow for better error messages. The argument
for this flag must be in the formx.y, for example,3.10.The syntax for using this in the config file ispython_version = "3.10".When the Python version is unspecified, Refurb uses whatever version your local Python installation uses.
For example, if yourpython --versionis3.11.5, Refurb uses3.11, dropping the5patch version.Changing Output FormatsBy default everything is outputted as plain text:file.py:1:5 [FURB123]: Replace `int(x)` with `x`Here are all of the available formats:text: The defaultgithub: Print output for use withGitHub AnnotationsMore to come!To change the default format use--format XYZon the command line, orformat = "XYZ"in the config file.Changing Sort OrderBy default errors are sorted by filename, then by error code. To change this, use the--sort XYZflag on
the command line, orsort_by = "XYZ"in the config file, whereXYZis one of the following sort modes:filename: Sort files in alphabetical order (the default)error: Sort by error first, then by filenameOverriding Mypy FlagsThis is typically used for development purposes, but can also be used to better fine-tune Mypy from
within Refurb. Any command line arguments after--are passed to Mypy. For example:$ refurb files -- --show-tracebackThis tells Mypy to show a traceback if it crashes.You can also use this in the config file by assigning an array of values to themypy_argsfield.
Note that any Mypy arguments passed via the command line arguments will override themypy_argsfield in the config file.Configuring RefurbIn addition to the command line arguments, you can also add your settings in thepyproject.tomlfile.
For example, the following command line arguments:refurb file.py --ignore 100 --load some_module --quietCorresponds to the following in yourpyproject.tomlfile:[tool.refurb]ignore=[100]load=["some_module"]quiet=trueNow all you need to type isrefurb file.py!Note that the values in the config file will be merged with the values specified via the
command line. In the case of boolean arguments like--quiet, the command line arguments
take precedence. All other arguments (such asignoreandload) will be combined.You can use the--config-fileflag to tell Refurb to use a different config file from the
defaultpyproject.tomlfile. Note that it still must be in the same form as the normalpyproject.tomlfile.Clickhereto see some example config files.Ignore Checks Per File/FolderIf you have a large codebase you might want to ignore errors for certain files or folders,
which allows you to incrementally fix errors as you see fit. To do that, add the following
to yourpyproject.tomlfile:# these settings will be applied globally[tool.refurb]enable_all=true# these will only be applied to the "src" folder[[tool.refurb.amend]]path="src"ignore=["FURB123","FURB120"]# these will only be applied to the "src/util.py" file[[tool.refurb.amend]]path="src/util.py"ignore=["FURB125","FURB148"]Note that only theignorefield is available in theamendsections. This is because
a check can only be enabled/disabled for the entire codebase, and cannot be selectively
enabled/disabled on a per-file basis. Assuming a check is enabled though, you can simplyignorethe errors for the files of your choosing.Using Refurb Withpre-commitYou can use Refurb withpre-commitby adding the following
to your.pre-commit-config.yamlfile:-repo:https://github.com/dosisod/refurbrev:REVISIONhooks:-id:refurbReplacingREVISIONwith a version or SHA of your choosing (or leave it blank to
letpre-commitfind the most recent one for you).PluginsInstalling plugins for Refurb is very easy:$ pip install refurb-plugin-exampleWhererefurb-plugin-exampleis the name of the plugin. Refurb will automatically load
any installed plugins.To make your own Refurb plugin, see therefurb-plugin-examplerepositoryfor more info.Writing Your Own CheckIf you want to extend Refurb but don't want to make a full-fledged plugin,
you can easily create a one-off check file with therefurb gencommand.Note that this command uses thefzffuzzy-finder for getting user input,
so you will need toinstall fzfbefore continuing.Here is the basic overview for creating a new check using therefurb gencommand:First select the node type you want to acceptThen type in where you want to save the auto generated fileAdd your code to the new fileTo get an idea of what you need to add to your check, use the--debugflag to see the
AST representation for a given file (ie,refurb --debug file.py). Take a look at the
files in therefurb/checks/folder for some examples.Then, to load your new check, userefurb file.py --load your.path.hereNote that when using--load, you need to use dots in your argument, just like
importing a normal python module. Ifyour.path.hereis a directory, all checks
in that directory will be loaded. If it is a file, only that file will be loaded.TroubleshootingIf Refurb is running slow, use the--timing-statsflag to diagnose why:$ refurb file --timing-stats /tmp/stats.jsonThis will output a JSON file with the following information:Total time Mypy took to parse the modules (a majority of the time usually).Time Mypy spent parsing each module. Useful for finding very large/unused files.Time Refurb spent checking each module. These numbers should be very small (less than 100ms).Larger files naturally take longer to check, but files that take way too long should be
looked into, as an issue might only manifest themselves when a file reaches a certain size.Disable ColorColor output is enabled by default in Refurb. To disable it, do one of the following:Set theNO_COLORenv var.Use the--no-colorflag.Setcolor = falsein the config file.Pipe/redirect Refurb output to another program or file.Developing / ContributingSetupTo setup locally run:$ git clone https://github.com/dosisod/refurb
$ cd refurb
$ make installTests can be ran all at once usingmake, or you can run each tool on its own usingmake black,make flake8, and so on.Unit tests can be ran withpytestormake test.Since the end-to-end (e2e) tests are slow, they are not ran when runningmake.
You will need to runmake test-e2eto run them.Updating DocumentationWe encourage people to update the documentation when they see typos and other issues!With that in mind though, don't directly modify thedocs/checks.mdfile. It is auto-generated
and will be overridden when new checks are added. The documentation for checks can be updated
by changing the docstrings of in the checks themselves. For example, to updateFURB100,
change the docstring of theErrorInfoclass in therefurb/checks/pathlib/with_suffix.pyfile.
You can find the file for a given check by grep-ing forcode = XYZ, whereXYZis the check
you are looking for but with theFURBprefix removed.Use the--verboseflag with--explainto find the filename for a given check. For example:$ refurb --explain FURB123 --verbose
Filename: refurb/checks/readability/no_unnecessary_cast.py

FURB123: no-redundant-cast [readability]

...Why Does This Exist?I love doing code reviews: I like taking something and making it better, faster, more
elegant, and so on. Lots of static analysis tools already exist, but none of them seem
to be focused on making code more elegant, more readable, or more modern. That is where
Refurb comes in.Refurb is heavily inspired byclippy,
the built-in linter for Rust.What Refurb Is NotRefurb is not a style/type checker. It is not meant as a first-line of defense for
linting and finding bugs, it is meant for making good code even better.Comparison To Other ToolsThere are already lots of tools out there for linting and analyzing Python code, so
you might be wondering why Refurb exists (skepticism is good!). As mentioned above,
Refurb checks for code which can be made more elegant, something that no other linters
(that I have found) specialize in. Here is a list of similar linters and analyzers,
and how they differ from Refurb:Black: is more focused on the formatting and
styling of the code (line length, trailing comas, indentation, and so on). It
does a really good job of making other projects using Black look more or less
the same. It doesn't do more complex things such as type checking or code
smell/anti-pattern detection.flake8: flake8 is also a linter, is very extensible,
and performs a lot of semantic analysis-related checks as well, such as "unused
variable", "break outside of a loop", and so on. It also checks PEP8
conformance. Refurb won't try and replace flake8, because chances are you
are already using flake8 anyways.Pylinthasa lot of checkswhich cover a lot of ground, but in general, are focused on bad or buggy
code, things which you probably didn't mean to do. Refurb assumes that you
know what you are doing, and will try to cleanup what is already there the best
it can.Mypy,Pyright,Pyre, andPytypeare all type checkers, and basically just enforce types, ensures arguments match,
functions are called in a type safe manner, and so on. They do much more then that, but
that is the general idea. Refurb actually is built on top of Mypy, and uses its AST
parser so that it gets good type information.pyupgrade: Pyupgrade has a lot of good
checks for upgrading your older Python code to the newer syntax, which is really
useful. Where Refurb differs is that Pyupgrade is more focused on upgrading your
code to the newer version, whereas Refurb is more focused on cleaning up and
simplifying what is already there.In conclusion, Refurb doesn't want you to throw out your old tools, since
they cover different areas of your code, and all serve a different purpose.
Refurb is meant to be used in conjunction with the above tools.