{"id":13468458,"url":"https://github.com/dosisod/refurb","last_synced_at":"2026-02-19T02:32:45.511Z","repository":{"id":52607219,"uuid":"518305561","full_name":"dosisod/refurb","owner":"dosisod","description":"A tool for refurbishing and modernizing Python codebases","archived":false,"fork":false,"pushed_at":"2026-02-17T18:41:57.000Z","size":832,"stargazers_count":2524,"open_issues_count":38,"forks_count":55,"subscribers_count":14,"default_branch":"master","last_synced_at":"2026-02-17T23:54:25.782Z","etag":null,"topics":["cli","gplv3","mypy","python","python310","python311","python312","testing"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dosisod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"dosisod"}},"created_at":"2022-07-27T04:13:51.000Z","updated_at":"2026-02-17T18:42:02.000Z","dependencies_parsed_at":"2023-10-03T12:09:19.593Z","dependency_job_id":"c9bbcf96-ef0c-4ec0-b216-9c5226118fc9","html_url":"https://github.com/dosisod/refurb","commit_stats":{"total_commits":399,"total_committers":17,"mean_commits":"23.470588235294116","dds":0.05513784461152882,"last_synced_commit":"db02242b142285e615a664a8d3324470bb711306"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/dosisod/refurb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosisod%2Frefurb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosisod%2Frefurb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosisod%2Frefurb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosisod%2Frefurb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dosisod","download_url":"https://codeload.github.com/dosisod/refurb/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosisod%2Frefurb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29601091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T00:59:38.239Z","status":"online","status_checked_at":"2026-02-19T02:00:07.702Z","response_time":117,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","gplv3","mypy","python","python310","python311","python312","testing"],"created_at":"2024-07-31T15:01:11.295Z","updated_at":"2026-02-19T02:32:45.480Z","avatar_url":"https://github.com/dosisod.png","language":"Python","readme":"# Refurb\n\nA tool for refurbishing and modernizing Python codebases.\n\n## Example\n\n```python\n# main.py\n\nfor filename in [\"file1.txt\", \"file2.txt\"]:\n    with open(filename) as f:\n        contents = f.read()\n\n    lines = contents.splitlines()\n\n    for line in lines:\n        if not line or line.startswith(\"# \") or line.startswith(\"// \"):\n            continue\n\n        for word in line.split():\n            print(f\"[{word}]\", end=\"\")\n\n        print(\"\")\n```\n\nRunning:\n\n```\n$ refurb main.py\nmain.py:3:17 [FURB109]: Use `in (x, y, z)` instead of `in [x, y, z]`\nmain.py:4:5 [FURB101]: Use `y = Path(x).read_text()` instead of `with open(x, ...) as f: y = f.read()`\nmain.py:10:40 [FURB102]: Replace `x.startswith(y) or x.startswith(z)` with `x.startswith((y, z))`\nmain.py:16:9 [FURB105]: Use `print() instead of `print(\"\")`\n```\n\n## Installing\n\n```\n$ pipx install refurb\n$ refurb file.py folder/\n```\n\n\u003e **Note**\n\u003e Refurb must be run on Python 3.10+, though it can check Python 3.7+ code by setting the `--python-version` flag.\n\n## Explanations For Checks\n\nYou can use `refurb --explain FURB123`, where `FURB123` is the error code you are trying to look up.\nFor example:\n\n````\n$ refurb --explain FURB123\nDon't cast a variable or literal if it is already of that type. For\nexample:\n\nBad:\n\n```\nname = str(\"bob\")\nnum = int(123)\n```\n\nGood:\n\n```\nname = \"bob\"\nnum = 123\n```\n````\n\nAn online list of all available checks can be viewed [here](./docs/checks.md).\n\n## Ignoring Errors\n\nUse `--ignore 123` to ignore error 123. The error code can be in the form `FURB123` or `123`.\nThis flag can be repeated.\n\n\u003e The `FURB` prefix indicates that this is a built-in error. The `FURB` prefix is optional,\n\u003e but for all other errors (ie, `ABC123`), the prefix is required.\n\nYou can also use inline comments to disable errors:\n\n```python\nx = int(0)  # noqa: FURB123\ny = list()  # noqa\n```\n\nHere, `noqa: FURB123` specifically ignores the FURB123 error for that line, and `noqa` ignores\nall errors on that line.\n\nYou can also specify multiple errors to ignore by separating them with a comma/space:\n\n```python\nx = not not int(0)  # noqa: FURB114, FURB123\nx = not not int(0)  # noqa: FURB114 FURB123\n```\n\n## Enabling/Disabling Checks\n\nCertain checks are disabled by default, and need to be enabled first. You can do this using the\n`--enable ERR` flag, where `ERR` is the error code of the check you want to enable. A disabled\ncheck differs from an ignored check in that a disabled check will never be loaded, whereas an\nignored check will be loaded, an error will be emitted, and the error will be suppressed.\n\nUse the `--verbose`/`-v` flag to get a full list of enabled checks.\n\nThe opposite of `--enable` is `--disable`, which will disable a check. When `--enable` and `--disable`\nare both specified via the command line, whichever one comes last will take precedence. When using\n`enable` and `disable` via the config file, `disable` will always take precedence.\n\nUse the `--disable-all` flag to disable all checks. This allows you to incrementally `--enable` checks\nas you see fit, as opposed to adding a bunch of `--ignore` flags. To use this in the config file,\nset `disable_all` to `true`.\n\nUse the `--enable-all` flag to enable all checks by default. This allows you to opt into all checks\nthat Refurb (and Refurb plugins) have to offer. This is a good option for new codebases. To use this\nin a config file, set `enable_all` to `true`.\n\nIn the config file, `disable_all`/`enable_all` is applied first, and then the `enable` and `disable`\nfields are applied afterwards.\n\n\u003e Note that `disable_all` and `enable_all` are mutually exclusive, both on the command line and in\n\u003e the config file. You will get an error if you try to specify both.\n\nYou can also disable checks by category using the `#category` syntax. For example, `--disable \"#readability\"`\nwill disable all checks with the `readability` category. The same applies for `enable` and `ignore`.\nAlso, if you disable an entire category you can still explicitly re-enable a check in that category.\n\n\u003e Note that `#readability` is wrapped in quotes because your shell will interpret the `#` as the\n\u003e start of a comment.\n\n## Setting Python Version\n\nUse the `--python-version` flag to tell Refurb which version of Python your codebase is using. This\nshould allow for better detection of language features, and allow for better error messages. The argument\nfor this flag must be in the form `x.y`, for example, `3.10`.\n\nThe syntax for using this in the config file is `python_version = \"3.10\"`.\n\nWhen the Python version is unspecified, Refurb uses whatever version your local Python installation uses.\nFor example, if your `python --version` is `3.11.5`, Refurb uses `3.11`, dropping the `5` patch version.\n\n## Changing Output Formats\n\nBy default everything is outputted as plain text:\n\n```\nfile.py:1:5 [FURB123]: Replace `int(x)` with `x`\n```\n\nHere are all of the available formats:\n\n* `text`: The default\n* `github`: Print output for use with [GitHub Annotations](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions)\n* More to come!\n\nTo change the default format use `--format XYZ` on the command line, or `format = \"XYZ\"` in the config file.\n\n## Changing Sort Order\n\nBy default errors are sorted by filename, then by error code. To change this, use the `--sort XYZ` flag on\nthe command line, or `sort_by = \"XYZ\"` in the config file, where `XYZ` is one of the following sort modes:\n\n* `filename`: Sort files in alphabetical order (the default)\n* `error`: Sort by error first, then by filename\n\n## Overriding Mypy Flags\n\nThis is typically used for development purposes, but can also be used to better fine-tune Mypy from\nwithin Refurb. Any command line arguments after `--` are passed to Mypy. For example:\n\n```\n$ refurb files -- --show-traceback\n```\n\nThis tells Mypy to show a traceback if it crashes.\n\nYou can also use this in the config file by assigning an array of values to the `mypy_args` field.\nNote that any Mypy arguments passed via the command line arguments will override the `mypy_args`\nfield in the config file.\n\n## Configuring Refurb\n\nIn addition to the command line arguments, you can also add your settings in the `pyproject.toml` file.\nFor example, the following command line arguments:\n\n```\nrefurb file.py --ignore 100 --load some_module --quiet\n```\n\nCorresponds to the following in your `pyproject.toml` file:\n\n```toml\n[tool.refurb]\nignore = [100]\nload = [\"some_module\"]\nquiet = true\n```\n\nNow all you need to type is `refurb file.py`!\n\nNote that the values in the config file will be merged with the values specified via the\ncommand line. In the case of boolean arguments like `--quiet`, the command line arguments\ntake precedence. All other arguments (such as `ignore` and `load`) will be combined.\n\nYou can use the `--config-file` flag to tell Refurb to use a different config file from the\ndefault `pyproject.toml` file. Note that it still must be in the same form as the normal\n`pyproject.toml` file.\n\nClick [here](./docs/configs/README.md) to see some example config files.\n\n### Ignore Checks Per File/Folder\n\nIf you have a large codebase you might want to ignore errors for certain files or folders,\nwhich allows you to incrementally fix errors as you see fit. To do that, add the following\nto your `pyproject.toml` file:\n\n```toml\n# these settings will be applied globally\n[tool.refurb]\nenable_all = true\n\n# these will only be applied to the \"src\" folder\n[[tool.refurb.amend]]\npath = \"src\"\nignore = [\"FURB123\", \"FURB120\"]\n\n# these will only be applied to the \"src/util.py\" file\n[[tool.refurb.amend]]\npath = \"src/util.py\"\nignore = [\"FURB125\", \"FURB148\"]\n```\n\n\u003e Note that only the `ignore` field is available in the `amend` sections. This is because\n\u003e a check can only be enabled/disabled for the entire codebase, and cannot be selectively\n\u003e enabled/disabled on a per-file basis. Assuming a check is enabled though, you can simply\n\u003e `ignore` the errors for the files of your choosing.\n\n## Using Refurb With `pre-commit`\n\nYou can use Refurb with [pre-commit](https://pre-commit.com/) by adding the following\nto your `.pre-commit-config.yaml` file:\n\n```yaml\n  - repo: https://github.com/dosisod/refurb\n    rev: REVISION\n    hooks:\n      - id: refurb\n```\n\nReplacing `REVISION` with a version or SHA of your choosing (or leave it blank to\nlet `pre-commit` find the most recent one for you).\n\n## Plugins\n\nInstalling plugins for Refurb is very easy:\n\n```\n$ pip install refurb-plugin-example\n```\n\nWhere `refurb-plugin-example` is the name of the plugin. Refurb will automatically load\nany installed plugins.\n\nTo make your own Refurb plugin, see the [`refurb-plugin-example` repository](https://github.com/dosisod/refurb-plugin-example)\nfor more info.\n\n## Writing Your Own Check\n\nIf you want to extend Refurb but don't want to make a full-fledged plugin,\nyou can easily create a one-off check file with the `refurb gen` command.\n\n\u003e Note that this command uses the `fzf` fuzzy-finder for getting user input,\n\u003e so you will need to [install fzf](https://github.com/junegunn/fzf#installation) before continuing.\n\nHere is the basic overview for creating a new check using the `refurb gen` command:\n\n1. First select the node type you want to accept\n2. Then type in where you want to save the auto generated file\n3. Add your code to the new file\n\nTo get an idea of what you need to add to your check, use the `--debug` flag to see the\nAST representation for a given file (ie, `refurb --debug file.py`). Take a look at the\nfiles in the `refurb/checks/` folder for some examples.\n\nThen, to load your new check, use `refurb file.py --load your.path.here`\n\n\u003e Note that when using `--load`, you need to use dots in your argument, just like\n\u003e importing a normal python module. If `your.path.here` is a directory, all checks\n\u003e in that directory will be loaded. If it is a file, only that file will be loaded.\n\n## Troubleshooting\n\nIf Refurb is running slow, use the `--timing-stats` flag to diagnose why:\n\n```\n$ refurb file --timing-stats /tmp/stats.json\n```\n\nThis will output a JSON file with the following information:\n\n* Total time Mypy took to parse the modules (a majority of the time usually).\n* Time Mypy spent parsing each module. Useful for finding very large/unused files.\n* Time Refurb spent checking each module. These numbers should be very small (less than 100ms).\n\nLarger files naturally take longer to check, but files that take way too long should be\nlooked into, as an issue might only manifest themselves when a file reaches a certain size.\n\n## Disable Color\n\nColor output is enabled by default in Refurb. To disable it, do one of the following:\n\n* Set the `NO_COLOR` env var.\n\n* Use the `--no-color` flag.\n\n* Set `color = false` in the config file.\n\n* Pipe/redirect Refurb output to another program or file.\n\n## Developing / Contributing\n\n### Setup\n\nTo setup locally run:\n\n```\n$ git clone https://github.com/dosisod/refurb\n$ cd refurb\n$ make install\n```\n\nTests can be ran all at once using `make`, or you can run each tool on its own using\n`make black`, `make flake8`, and so on.\n\nUnit tests can be ran with `pytest` or `make test`.\n\n\u003e Since the end-to-end (e2e) tests are slow, they are not ran when running `make`.\n\u003e You will need to run `make test-e2e` to run them.\n\n### Updating Documentation\n\nWe encourage people to update the documentation when they see typos and other issues!\n\nWith that in mind though, don't directly modify the `docs/checks.md` file. It is auto-generated\nand will be overridden when new checks are added. The documentation for checks can be updated\nby changing the docstrings of in the checks themselves. For example, to update `FURB100`,\nchange the docstring of the `ErrorInfo` class in the `refurb/checks/pathlib/with_suffix.py` file.\nYou can find the file for a given check by grep-ing for `code = XYZ`, where `XYZ` is the check\nyou are looking for but with the `FURB` prefix removed.\n\nUse the `--verbose` flag with `--explain` to find the filename for a given check. For example:\n\n```\n$ refurb --explain FURB123 --verbose\nFilename: refurb/checks/readability/no_unnecessary_cast.py\n\nFURB123: no-redundant-cast [readability]\n\n...\n```\n\n## Why Does This Exist?\n\nI love doing code reviews: I like taking something and making it better, faster, more\nelegant, and so on. Lots of static analysis tools already exist, but none of them seem\nto be focused on making code more elegant, more readable, or more modern. That is where\nRefurb comes in.\n\nRefurb is heavily inspired by [clippy](https://rust-lang.github.io/rust-clippy/master/index.html),\nthe built-in linter for Rust.\n\n## What Refurb Is Not\n\nRefurb is not a style/type checker. It is not meant as a first-line of defense for\nlinting and finding bugs, it is meant for making good code even better.\n\n## Comparison To Other Tools\n\nThere are already lots of tools out there for linting and analyzing Python code, so\nyou might be wondering why Refurb exists (skepticism is good!). As mentioned above,\nRefurb checks for code which can be made more elegant, something that no other linters\n(that I have found) specialize in. Here is a list of similar linters and analyzers,\nand how they differ from Refurb:\n\n[Black](https://github.com/psf/black): is more focused on the formatting and\nstyling of the code (line length, trailing comas, indentation, and so on). It\ndoes a really good job of making other projects using Black look more or less\nthe same. It doesn't do more complex things such as type checking or code\nsmell/anti-pattern detection.\n\n[flake8](https://github.com/pycqa/flake8): flake8 is also a linter, is very extensible,\nand performs a lot of semantic analysis-related checks as well, such as \"unused\nvariable\", \"break outside of a loop\", and so on. It also checks PEP8\nconformance. Refurb won't try and replace flake8, because chances are you\nare already using flake8 anyways.\n\n[Pylint](https://github.com/PyCQA/pylint) has [a lot of checks](https://pylint.pycqa.org/en/latest/user_guide/messages/messages_overview.html)\nwhich cover a lot of ground, but in general, are focused on bad or buggy\ncode, things which you probably didn't mean to do. Refurb assumes that you\nknow what you are doing, and will try to cleanup what is already there the best\nit can.\n\n[Mypy](https://github.com/python/mypy), [Pyright](https://github.com/Microsoft/pyright),\n[Pyre](https://github.com/facebook/pyre-check), and [Pytype](https://github.com/google/pytype)\nare all type checkers, and basically just enforce types, ensures arguments match,\nfunctions are called in a type safe manner, and so on. They do much more then that, but\nthat is the general idea. Refurb actually is built on top of Mypy, and uses its AST\nparser so that it gets good type information.\n\n[pyupgrade](https://github.com/asottile/pyupgrade): Pyupgrade has a lot of good\nchecks for upgrading your older Python code to the newer syntax, which is really\nuseful. Where Refurb differs is that Pyupgrade is more focused on upgrading your\ncode to the newer version, whereas Refurb is more focused on cleaning up and\nsimplifying what is already there.\n\nIn conclusion, Refurb doesn't want you to throw out your old tools, since\nthey cover different areas of your code, and all serve a different purpose.\nRefurb is meant to be used in conjunction with the above tools.\n","funding_links":["https://github.com/sponsors/dosisod"],"categories":["Python","Programming Languages","Static Checks","Code Analysis"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdosisod%2Frefurb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdosisod%2Frefurb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdosisod%2Frefurb/lists"}