Last active
October 31, 2024 07:22
-
-
Save jbwhit/eecdd1cac2756df85ad165f437445b0b to your computer and use it in GitHub Desktop.
Steps to use `ruff` in JupyterLab with the `jupyterlab_code_formatter` plugin.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 | |
} |
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.
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?
I had to add the import "from typing import List" to the config file snippet. @jbwhit
Thanksk @nicornk -- I've added that to my snippet!
❤️
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"
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
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 thesubprocess.run()
call.