Skip to content

Commit e19c659

Browse files
authored
[Feature] add execution time to autoformat (#23)
1 parent 0a3a3db commit e19c659

File tree

7 files changed

+119
-92
lines changed

7 files changed

+119
-92
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ repos:
1111
- id: check-merge-conflict
1212

1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.14.8
14+
rev: v0.15.2
1515
hooks:
1616
- id: ruff-check
17+
name: ruff check fix
1718
args: [ --fix ]
19+
- id: ruff-check
20+
name: ruff isort
21+
args: [--select, I, --fix]
1822
- id: ruff-format
19-
20-
- repo: https://github.com/pycqa/isort
21-
rev: 7.0.0
22-
hooks:
23-
- id: isort
24-
args: ["--profile", "black"]

README.md

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@
55

66
## Overview
77

8-
**`colablinter`** is an **IPython magic command extension** designed specifically for Jupyter and Google Colab notebooks.
9-
10-
It integrates the high-speed linter **`ruff`** to perform code quality checks and enforce standards directly within Jupyter/Colab cells.
11-
12-
It allows developers to lint and format code on a **cell-by-cell** basis or check the **entire notebook** with simple commands.
8+
**`colablinter`** is an **IPython magic command extension** designed for Jupyter and Google Colab notebooks.
9+
It integrates the high-speed linter **`ruff`** to perform code quality checks and formatting directly within Jupyter/Colab cells.
10+
It allows developers to lint code on a **cell-by-cell** basis or the **entire notebook** with simple commands.
1311

1412
## Magic cell Commands
1513

16-
| Command | Type | Description |
17-
| :--- | :--- | :--- |
18-
| **`%%cfix`** | Cell Magic | Fixes and Formats the current cell's code. |
19-
| **`%%creport`** | Cell Magic | Displays a linting report for the current cell. |
20-
| **`%clautofix`** | Line Magic | Activates or deactivates automatic code fixing and formatting before every cell execution. |
21-
| **`%clreport`** | Line Magic | Displays a linting report for the **entire saved notebook** (requires Google Drive mount). |
14+
| Command | Description |
15+
| :--- | :--- |
16+
| **`%%cformat`** | Sorts imports and Formats the current cell's code. |
17+
| **`%%ccheck`** | Displays a linting report for the current cell. |
18+
| **`%lautoformat`** | Activates or deactivates automatic import sorting, formatting, and execution time display before every cell. |
19+
| **`%lcheck`** | Displays a linting report for the **entire saved notebook** (requires Google Drive mount). |
2220

23-
After executing a cell magic command, the fixed/reported code is immediately executed (if applicable), maintaining the notebook workflow.
21+
After executing a cell magic command, the checked/formatted code is immediately executed (if applicable), maintaining the notebook workflow.
2422

2523
## Installation
2624

@@ -31,18 +29,18 @@ pip install colablinter
3129
```
3230

3331
## Usage
34-
The extension must be explicitly loaded in the notebook session before use. Once the extension is loaded, `%clautofix` is triggered automatically.
32+
The extension must be explicitly loaded in the notebook session before use. Once the extension is loaded, `%lautoformat` is activated by default.
3533

3634
```python
3735
%load_ext colablinter
3836
```
3937

4038

41-
1. Fix and Format cell (`%%cfix`)
39+
1. Sorts imports and Formats cell (`%%cformat`)
4240

43-
`%%cfix` corrects code and runs the formatter. The cell executes the fixed code.
41+
`%%cformat` corrects code and runs the formatter. The cell executes after cell is formatted.
4442
```python
45-
%%cfix
43+
%%cformat
4644
import math, sys;
4745

4846
class Example( object ):
@@ -70,11 +68,11 @@ The extension must be explicitly loaded in the notebook session before use. Once
7068
return (sys.path, some_string)
7169
```
7270

73-
2. Check cell quality (`%%creport`)
71+
2. Check cell quality (`%%ccheck`)
7472

75-
Use `%%creport` to see linting reports for the code below the command. After the report is displayed, the code in the cell executes as normal.
73+
Use `%%ccheck` to see linting reports for the code below the command. After the report is displayed, the code in the cell executes as normal.
7674
```python
77-
%%creport
75+
%%ccheck
7876

7977
def invalid_code(x):
8078
return x + y # 'y' is not defined
@@ -95,23 +93,23 @@ The extension must be explicitly loaded in the notebook session before use. Once
9593
**Note on F401:**
9694
The linter is explicitly configured to **ignore F401 errors** (unused imports). This is to ensure compatibility with the stateful nature of Jupyter/Colab notebooks, where imports in one cell may be necessary for code execution in subsequent cells, preventing unintended breakage of the notebook's execution flow.
9795

98-
3. Activate/Deactivate Auto Fix (`%clautofix`)
96+
3. Activate/Deactivate Auto Fix (`%lautoformat`)
9997

100-
The `%clautofix` line magic allows you to automatically fix code before every code cell is executed.
98+
The `%lautoformat` line magic allows you to automatically fix code before every code cell is executed.
10199

102100
To Activate Auto Fixing:
103101
```python
104-
%clautofix on # %clautofix off when you want to deactivate
102+
%lautoformat on # %lautoformat off when you want to deactivate
105103
```
106104

107-
4. Check entire notebook (`%clreport`)
105+
4. Check entire notebook (`%lcheck`)
108106

109-
Use line magic `%clreport` to check across the entire saved notebook file (requires the notebook to be saved to Google Drive and mounted).
107+
Use line magic `%lcheck` to check across the entire saved notebook file (requires the notebook to be saved to Google Drive and mounted).
110108

111109
```python
112-
%clreport /content/drive/MyDrive/Colab Notebooks/path/to/notebook.ipynb
110+
%lcheck /content/drive/MyDrive/Colab Notebooks/path/to/notebook.ipynb
113111
```
114112

115113
## Known Caveats & Troubleshooting
116114

117-
Magic Command Execution: When using magic or terminal commands with `%clautofix` on active, the autofix mechanism is temporarily suppressed during the final execution step to prevent infinite loops or dual checks. If you want to disable auto-fixing, use `%clautofix off`
115+
Magic Command Execution: When using magic or terminal commands while `%lautoformat` is active, the auto-format mechanism is temporarily suppressed during the final execution step to prevent infinite loops or dual checks. If you want to disable auto-formatting, use `%lautoformat off`

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "colablinter"
3-
version = "0.1.7"
3+
version = "0.2.0"
44
description = "Linting and formatting Python code in Google Colab."
55
readme = "README.md"
66
authors = [

src/colablinter/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
def load_ipython_extension(ipython: InteractiveShell):
99
ipython.register_magics(ColabLinterMagics)
1010
logger.info("All commands registered.")
11-
ipython.run_line_magic("clautofix", "on")
11+
ipython.run_line_magic("lautoformat", "on")
1212

1313
except Exception as e:
1414
logger.exception(f"Initialization failed: {e}")

src/colablinter/command.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
_FILE_NAME = "notebook_cell.py"
66
_RULESET = "F,E,I,B"
7-
_CELL_REPORT_COMMAND = f"ruff check --select {_RULESET} --ignore F401 --line-length 100 --stdin-filename={_FILE_NAME}"
8-
_CELL_CHECK_COMMAND = f"ruff check --select I --stdin-filename={_FILE_NAME} --fix"
7+
_CELL_CHECK_COMMAND = (
8+
f"ruff check --select {_RULESET} --ignore F401,E501 --stdin-filename={_FILE_NAME}"
9+
)
10+
_CELL_CHECK_ISORT_COMMAND = f"ruff check --select I --stdin-filename={_FILE_NAME} --fix"
911
_CELL_FORMAT_COMMAND = f"ruff format --stdin-filename={_FILE_NAME}"
1012
_NOTEBOOK_REPORT_COMMAND = (
11-
f"ruff check --select {_RULESET} --line-length 100 '{{notebook_path}}'"
13+
f"ruff check --select {_RULESET} --ignore E501 '{{notebook_path}}'"
1214
)
1315

1416

@@ -37,16 +39,16 @@ def execute_command(command: str, input_data: str) -> str | None:
3739
return None
3840

3941

40-
def cell_report(cell: str) -> None:
41-
report = execute_command(_CELL_REPORT_COMMAND, input_data=cell)
42+
def cell_check(cell: str) -> None:
43+
report = execute_command(_CELL_CHECK_COMMAND, input_data=cell)
4244
if report:
4345
logger.info(report)
4446
else:
4547
logger.info("No issues found. Code is clean.")
4648

4749

48-
def cell_check(cell: str) -> str | None:
49-
fixed_code = execute_command(_CELL_CHECK_COMMAND, input_data=cell).strip()
50+
def cell_check_isort(cell: str) -> str | None:
51+
fixed_code = execute_command(_CELL_CHECK_ISORT_COMMAND, input_data=cell).strip()
5052
if fixed_code.strip():
5153
return fixed_code.strip()
5254
return None

src/colablinter/magics.py

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import os
2+
import time
23

3-
from IPython.core.interactiveshell import ExecutionInfo
4+
from IPython.core.interactiveshell import ExecutionInfo, ExecutionResult
45
from IPython.core.magic import Magics, cell_magic, line_magic, magics_class
56

67
from colablinter.command import (
78
cell_check,
9+
cell_check_isort,
810
cell_format,
9-
cell_report,
1011
notebook_report,
1112
)
1213
from colablinter.logger import logger
@@ -36,61 +37,73 @@ def _ensure_drive_mounted():
3637
) from e
3738

3839

40+
class CellTimer:
41+
def __init__(self):
42+
self.start_time = None
43+
44+
def start(self, info: ExecutionInfo | None = None):
45+
self.start_time = time.perf_counter()
46+
47+
def stop(self, result: ExecutionResult | None = None):
48+
if self.start_time:
49+
duration = time.perf_counter() - self.start_time
50+
logger.info(f"\033[92m✔ Done | {duration:.3f}s\033[0m")
51+
self.start_time = None
52+
53+
3954
@magics_class
4055
class ColabLinterMagics(Magics):
4156
def __init__(self, **kwargs) -> None:
4257
super().__init__(**kwargs)
43-
self._is_autofix_active = False
58+
self._is_autoformat_active = True
59+
self.timer = CellTimer()
4460

4561
@cell_magic
46-
def creport(self, line: str, cell: str) -> None:
62+
def ccheck(self, line: str, cell: str) -> None:
4763
stripped_cell = cell.strip()
48-
cell_report(stripped_cell)
64+
cell_check(stripped_cell)
4965
self.__execute(stripped_cell)
5066

5167
@cell_magic
52-
def cfix(self, line: str, cell: str) -> None:
68+
def cformat(self, line: str, cell: str) -> None:
5369
stripped_cell = cell.strip()
5470
if _is_invalid_cell(stripped_cell):
5571
logger.info(
56-
"Fix skipped. Cell starts with magic (%, %%) or shell (!...) command."
72+
"Format skipped. Cell starts with magic (%, %%) or shell (!...) command."
5773
)
5874
self.__execute(stripped_cell)
5975
return None
6076

61-
fixed_code = cell_check(stripped_cell)
62-
if fixed_code is None:
63-
logger.error("Linter check failed. Code not modified.")
77+
formatted_code = cell_format(stripped_cell)
78+
if formatted_code is None:
79+
logger.error("Formatter failed. Code not modified.")
6480
self.__execute(stripped_cell)
6581
return None
6682

67-
formatted_code = cell_format(fixed_code)
68-
if formatted_code:
69-
self.shell.set_next_input(formatted_code, replace=True)
70-
self.__execute(formatted_code)
71-
else:
72-
logger.error("Formatter failed. Check-fixed code executed.")
83+
fixed_code = cell_check_isort(formatted_code)
84+
if fixed_code:
85+
self.shell.set_next_input(fixed_code, replace=True)
7386
self.__execute(fixed_code)
87+
else:
88+
logger.error("Formatter failed. Formatted code executed.")
89+
self.__execute(formatted_code)
7490

7591
@line_magic
76-
def clautofix(self, line: str) -> None:
92+
def lautoformat(self, line: str) -> None:
7793
action = line.strip().lower()
7894
if action == "on":
79-
self.shell.events.register("pre_run_cell", self.__autofix)
80-
self._is_autofix_active = True
81-
logger.info("Auto-fix activated for pre-run cells.")
95+
self.__register()
96+
self._is_autoformat_active = True
97+
logger.info("Auto-format activated for pre-run cells.")
8298
elif action == "off":
83-
try:
84-
self.shell.events.unregister("pre_run_cell", self.__autofix)
85-
except Exception:
86-
pass
87-
self._is_autofix_active = False
88-
logger.info("Auto-fix deactivated.")
99+
self.__unregister()
100+
self._is_autoformat_active = False
101+
logger.info("Auto-format deactivated.")
89102
else:
90-
logger.info("Usage: %clautofix on or %clautofix off.")
103+
logger.info("Usage: %lautoformat on or %lautoformat off.")
91104

92105
@line_magic
93-
def clreport(self, line: str) -> None:
106+
def lcheck(self, line: str) -> None:
94107
_ensure_drive_mounted()
95108
notebook_path = line.strip().strip("'").strip('"')
96109
if not notebook_path:
@@ -113,40 +126,56 @@ def clreport(self, line: str) -> None:
113126
logger.info("-------------------------------------------------------------")
114127

115128
def __execute(self, cell: str) -> None:
116-
if self._is_autofix_active:
129+
if self._is_autoformat_active:
117130
logger.info(
118-
"Autofix is temporarily suppressed to prevent dual execution. "
119-
"To disable, run: %clautofix off"
131+
"autoformat is temporarily suppressed to prevent dual execution. "
132+
"To disable, run: %lautoformat off"
120133
)
121-
try:
122-
self.shell.events.unregister("pre_run_cell", self.__autofix)
123-
except ValueError:
124-
pass
134+
self.__unregister()
125135
try:
126136
self.shell.run_cell(cell, silent=False, store_history=True)
127137
except Exception as e:
128138
logger.exception(f"Code execution failed: {e}")
129139
finally:
130-
if self._is_autofix_active:
131-
try:
132-
self.shell.events.register("pre_run_cell", self.__autofix)
133-
except Exception:
134-
pass
140+
if self._is_autoformat_active:
141+
self.__register()
135142

136-
def __autofix(self, info: ExecutionInfo) -> None:
143+
def __autoformat(self, info: ExecutionInfo) -> None:
137144
stripped_cell = info.raw_cell.strip()
138145
if _is_invalid_cell(stripped_cell):
139-
logger.info("Autofix is skipped for cell with magic or terminal.")
146+
logger.info("autoformat is skipped for cell with magic or terminal.")
140147
return None
141148

142-
fixed_code = cell_check(stripped_cell)
143-
if fixed_code is None:
144-
logger.error("Linter check failed during auto-fix.")
149+
formatted_code = cell_format(stripped_cell)
150+
if formatted_code is None:
151+
logger.error("Formatter failed during auto-format.")
145152
return None
146153

147-
formatted_code = cell_format(fixed_code)
148-
if formatted_code is None:
149-
logger.error("Formatter failed during auto-fix.")
154+
fixed_code = cell_check_isort(formatted_code)
155+
if fixed_code is None:
156+
logger.error("Linter check failed during auto-format.")
150157
return None
151158

152-
self.shell.set_next_input(formatted_code, replace=True)
159+
self.shell.set_next_input(fixed_code, replace=True)
160+
161+
def __register(self) -> None:
162+
for event, callback in [
163+
("pre_run_cell", self.__autoformat),
164+
("pre_run_cell", self.timer.start),
165+
("post_run_cell", self.timer.stop),
166+
]:
167+
try:
168+
self.shell.events.register(event, callback)
169+
except Exception:
170+
pass
171+
172+
def __unregister(self) -> None:
173+
for event, callback in [
174+
("pre_run_cell", self.__autoformat),
175+
("pre_run_cell", self.timer.start),
176+
("post_run_cell", self.timer.stop),
177+
]:
178+
try:
179+
self.shell.events.unregister(event, callback)
180+
except Exception:
181+
pass

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)