Skip to content

Instantly share code, notes, and snippets.

@jbwhit
Last active October 31, 2024 07:22
Show Gist options
  • Save jbwhit/eecdd1cac2756df85ad165f437445b0b to your computer and use it in GitHub Desktop.
Save jbwhit/eecdd1cac2756df85ad165f437445b0b to your computer and use it in GitHub Desktop.
Steps to use `ruff` in JupyterLab with the `jupyterlab_code_formatter` plugin.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "f98530b7-48d0-4cec-994c-cc0782ddcb2d",
"metadata": {},
"source": [
"# Using `ruff` in JupyterLab with `jupyterlab_code_formatter`\n",
"\n",
"- **ruff** formatter: https://docs.astral.sh/ruff/ \n",
"- **jupyterlab_code_formatter** JupyterLab plugin: https://jupyterlab-code-formatter.readthedocs.io/\n",
"\n",
"\n",
"# Watch the walkthrough video for details: https://youtu.be/FXA6PJUabKQ "
]
},
{
"cell_type": "markdown",
"id": "753ec684-e78a-4820-ab86-eec0ff4d394f",
"metadata": {},
"source": [
"## Install `jupyterlab_code_formatter` \n",
"\n",
"This snippet creates a conda (mamba) environment called `example`.\n",
"\n",
"```bash\n",
"mamba create -n example python=3.11\n",
"mamba activate example\n",
"mamba install -c conda-forge black ruff isort jupyterlab_code_formatter jupyterlab\n",
"```\n",
"\n",
"## Generate (or if it exists, update) your jupyter config\n",
"\n",
"This command will generate (or if it already exists ask if you want to overwrite) a jupyter config for your system.\n",
"\n",
"```bash\n",
"jupyter lab --generate-config\n",
"```\n",
"\n",
"This command returns the path to the new config.\n",
"\n",
"## Copy and paste this code snippet to the end of your jupyter config file:\n",
"\n",
"Special thanks to [Ryan Tam](https://twitter.com/ryantam626) for providing not only the `jupyterlab_code_formatter` plugin itself, but the modification code snippet below to make the new features of ruff available.\n",
"\n",
"Misc links: \n",
"\n",
"```python\n",
"from typing import List\n",
"from jupyterlab_code_formatter.formatters import BaseFormatter, handle_line_ending_and_magic, SERVER_FORMATTERS, logger\n",
"import subprocess\n",
"\n",
"class RuffFormatFormatter(BaseFormatter):\n",
"\n",
" label = \"Apply Ruff Format Formatter - Confirmed working for 0.1.3\"\n",
" \n",
" def __init__(self) -> None:\n",
" try:\n",
" from ruff.__main__ import find_ruff_bin\n",
"\n",
" self.ruff_bin = find_ruff_bin()\n",
" except (ImportError, FileNotFoundError):\n",
" self.ruff_bin = \"ruff\"\n",
"\n",
" @property\n",
" def importable(self) -> bool:\n",
" return True\n",
"\n",
" @handle_line_ending_and_magic\n",
" def format_code(self, code: str, notebook: bool, args: List[str] = [], **options) -> str:\n",
" process = subprocess.run(\n",
" [self.ruff_bin, \"format\", \"-\"],\n",
" input=code,\n",
" stdout=subprocess.PIPE,\n",
" stderr=subprocess.PIPE,\n",
" universal_newlines=True,\n",
" encoding=\"utf-8\",\n",
" )\n",
"\n",
" if process.stderr:\n",
" logger.info(process.stderr)\n",
" return code\n",
" else:\n",
" return process.stdout\n",
"\n",
" \n",
"SERVER_FORMATTERS[\"ruff_format\"] = RuffFormatFormatter()\n",
"```\n",
"\n",
"Remember to restart your jupyter instance. "
]
},
{
"cell_type": "markdown",
"id": "36a0c2dd-7432-49d3-a948-60c3f1e2ba4b",
"metadata": {},
"source": [
"## Start jupyter\n",
"\n",
" Edit the settings \n",
" > click the JSON editor \n",
" > Jupyterlab Code Formatter\n",
" > Edit the python default formatter to say \"ruff_format\"\n",
"\n",
"The top few lines will look like this after you've made this edit: \n",
"\n",
"```JSON\n",
"{\n",
" \"preferences\": {\n",
" \"default_formatter\": {\n",
" \"python\": \"ruff_format\",\n",
"```\n",
"\n",
"And now if you click the button in the toolbar, it will format the whole notebook using `ruff`. "
]
},
{
"cell_type": "markdown",
"id": "ff6b2d90-69b1-4302-a2ee-bff168939290",
"metadata": {},
"source": [
"# Bonus -- if you want to run the formatter on only a single cell with a keyboard shortcut\n",
"\n",
"## Add this snippet to the Jupyter keyboard shortcuts to enable formatting a single cell\n",
"\n",
" Edit the settings \n",
" > click the JSON editor \n",
" > Keyboard Shortcuts\n",
" > Edit the right half (User preferences)\n",
"\n",
"copy and paste this snippet to bind `Ctrl K` to format the cell.\n",
"\n",
"```json\n",
" {\n",
" \"command\": \"jupyterlab_code_formatter:format\",\n",
" \"keys\": [\n",
" \"Ctrl K\"\n",
" ],\n",
" \"selector\": \".jp-Notebook.jp-mod-editMode\",\n",
" \"args\": {}\n",
" },\n",
"```\n",
"\n",
"\n",
"Note, you have to be in Edit mode in the cell for this shortcut to work. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5195f92-e679-4f1e-b3cd-94ce7e2ecc5c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "python3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@dlimpid
Copy link

dlimpid commented Jan 25, 2024

Thank you very much for the great guide! A problem for me was that cells containing non-ascii unicode characters were not formatted. I could fix this by adding encoding="utf-8" in the subprocess.run() call.

@jbwhit
Copy link
Author

jbwhit commented Jan 25, 2024

Thank you for the comment! I'm thinking of revisiting this and seeing if I can make a PR to the extension itself. I'll add your change.

@Gr1nse
Copy link

Gr1nse commented Mar 26, 2024

Hi!
Nice Guide!
in this Guide you just use the formater right?
Do you know how to get the ruff linter to work in jupyterlab?

@nicornk
Copy link

nicornk commented Apr 19, 2024

I had to add the import "from typing import List" to the config file snippet. @jbwhit

@jbwhit
Copy link
Author

jbwhit commented Apr 19, 2024

Thanksk @nicornk -- I've added that to my snippet!

@aliavni
Copy link

aliavni commented Apr 23, 2024

❤️

@amano-takahisa
Copy link

I am very happy with your config that can also reflect ruff configs written in pyproject.toml such as:

[tool.ruff.format]
quote-style = "single"

@brurucy
Copy link

brurucy commented Jul 17, 2024

If you guys also want it to run check before format, then use this:

    @handle_line_ending_and_magic
    def format_code(
        self, code: str, notebook: bool, args: List[str] = [], **options
    ) -> str:
        # Lint
        linting = subprocess.run(
            [self.ruff_bin, "check", "--fix", "--exit-zero", "-"],
            input=code,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
            encoding="utf-8",
        )
        
        # Format
        linted_code = linting.stdout
        process = subprocess.run(
            [self.ruff_bin, "format", "-"],
            input=linted_code,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
            encoding="utf-8",
        )

        if process.stderr:
            logger.info(process.stderr)
            return code
        else:
            return process.stdout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment