tqdm — Cursor rule: progress bars in Python done right
A Cursor rules file that pins down how the agent should use tqdm in Python: imports, cleanup, console output and live metrics in the postfix.
.cursor/rules/tqdm.mdcWhat tqdm does
The rule lives in .cursor/rules/tqdm.mdc and fires on Python files matched by **/*.py. It ships eight sections, each with a bad and a good example: importing, context managers, manual updates, labels, tqdm.write() output, nested bars, pandas hooks and a note on speed.
In practice the agent drops import tqdm plus tqdm.tqdm(...) in favour of from tqdm import tqdm, trange. A bar gets wrapped in with ... as pbar: so it closes on exceptions. Manual pbar.update(n) calls must come with total, otherwise the percentage and ETA stay blank. Printing mid-loop means tqdm.write(), since plain print() smears an active bar across the terminal.
Nested loops get leave=False; byte counters get unit="B" alongside unit_scale=True. On tight loops the advice is to update per batch rather than per item. tqdm.pandas() registers .progress_apply() on a DataFrame, tqdm.auto picks terminal or notebook rendering on its own. None of the snippets run without the tqdm package installed, so add the dependency before you lean on the rule.
What it helps with
- keep progress bars consistent across a codebase
- stop
print()calls from wrecking an active bar - close the bar when a loop raises
- label file downloads and training runs with live metrics
- show progress for pandas operations and notebooks
Example prompts for the agent
- Wrap the training loop in a tqdm bar per the rule, with a context manager and
set_postfixfor loss. - Swap the
print()inside this loop fortqdm.write(). - Add progress to
df.progress_applyand hide nested bars withleave=False.
How to install tqdm
Manually: put the files in place
.cursor/rules/tqdm.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/tqdm.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/tqdm.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/tqdm.mdManually: put the files in place
.clinerules/tqdm.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about tqdm
How do I install the tqdm rule in Cursor?
Place .cursor/rules/tqdm.mdc inside the .cursor/rules folder of your project. Cursor picks it up and applies it to **/*.py files.
What does the tqdm rule do?
It gives the Cursor agent a fixed set of conventions for tqdm: direct imports, context managers instead of calling close() by hand, total whenever updates are manual, and tqdm.write() for messages.
Why avoid print() while a bar is running?
It collides with the bar's own output and breaks the line. The rule routes messages through tqdm.write() so the display stays intact.
Source text
tqdm.mdc · CC0-1.0
---
description: This guide defines best practices for using the `tqdm` library to implement clear, efficient, and robust progress bars in Python applications, ensuring consistent user feedback and maintainable code.
globs: **/*.py
---
# tqdm Best Practices
`tqdm` is the definitive library for adding progress bars to Python applications. It's lightweight, fast, and works across various environments (terminal, Jupyter). This guide outlines the mandatory practices for its use.
## 1. Import Strategy
Always import `tqdm` directly from the library for clarity. Use `trange` as a convenient shortcut for `tqdm(range(N))`.
❌ BAD: Generic import
```python
import tqdm
for i in tqdm.tqdm(range(100)):
pass
```
✅ GOOD: Direct and clear imports
```python
from tqdm import tqdm, trange
for i in trange(100): # Use trange for simple range loops
pass
data = [1, 2, 3] * 10
for item in tqdm(data, desc="Processing items"): # Use tqdm for iterables
pass
```
## 2. Prefer Context Managers
Use `tqdm` as a context manager (`with ... as pbar:`) whenever possible. This ensures the progress bar is properly closed and resources are released, even if exceptions occur.
❌ BAD: Manual `close()` (prone to errors)
```python
pbar = tqdm(total=100)
for i in range(100):
pbar.update(1)
# What if an error occurs here? pbar.close() might be skipped.
pbar.close()
```
✅ GOOD: Context manager for robust cleanup
```python
with tqdm(total=100, desc="Calculating") as pbar:
for i in range(100):
# Simulate work
if i == 50:
# Example of an error, pbar will still close
# raise ValueError("Something went wrong!")
pass
pbar.update(1)
```
## 3. Manual Updates for Non-Iterables or Custom Logic
When wrapping an iterable isn't feasible (e.g., callbacks, async operations, or custom progress logic), use `pbar.update(n)`. **Always provide `total`** when using manual updates.
❌ BAD: Missing `total` with manual updates (bar won't show percentage/ETA)
```python
pbar = tqdm(desc="Downloading")
# ... later in a callback ...
pbar.update(chunk_size)
```
✅ GOOD: Explicit `total` for manual updates
```python
import time
import random
file_size = 1024 * 1024 * 50 # 50 MB
downloaded_bytes = 0
with tqdm(total=file_size, unit="B", unit_scale=True, desc="Downloading file") as pbar:
while downloaded_bytes < file_size:
chunk = random.randint(1024, 1024 * 10) # Simulate chunk download
chunk = min(chunk, file_size - downloaded_bytes)
downloaded_bytes += chunk
pbar.update(chunk)
time.sleep(0.01)
```
## 4. Use Descriptive Labels and Postfix Information
Enhance readability by providing a `desc` (description) and dynamic `postfix` information. `set_description()` and `set_postfix()` are preferred for updating these dynamically.
❌ BAD: Static or cluttered description
```python
for epoch in tqdm(range(10), desc="Epoch"):
loss = 0.5 - epoch * 0.01
# This doesn't update dynamically without re-creating the bar
for batch in tqdm(range(100), desc=f"Epoch {epoch} Loss: {loss:.4f}", leave=False):
pass
```
✅ GOOD: Dynamic description and postfix updates
```python
import random
for epoch in tqdm(range(10), desc="Training Epochs"):
total_loss = 0.0
for batch in trange(100, desc=f"Batch {epoch+1}", leave=False):
current_loss = random.uniform(0.1, 0.5)
total_loss += current_loss
# Update postfix with real-time metrics
tqdm.set_postfix(loss=f"{current_loss:.4f}", avg_loss=f"{total_loss/(batch+1):.4f}")
# Simulate work
time.sleep(0.005)
```
## 5. Handle Output with `tqdm.write()`
Never use `print()` directly when a `tqdm` bar is active. It will corrupt the progress bar display. Use `tqdm.write()` instead, which intelligently prints messages without interfering.
❌ BAD: Corrupts progress bar
```python
for i in trange(10):
if i == 5:
print("Mid-loop message!") # This will mess up the bar
time.sleep(0.1)
```
✅ GOOD: Preserves progress bar integrity
```python
for i in trange(10, desc="Processing"):
if i == 5:
tqdm.write("Mid-loop message: Halfway there!")
time.sleep(0.1)
```
## 6. Fine-tune Display Options
Use `leave=False` for inner loops to remove them upon completion, keeping the terminal clean. Leverage `unit`, `unit_scale`, and `dynamic_ncols` for better presentation.
❌ BAD: Cluttered terminal with nested bars
```python
for i in tqdm(range(3), desc="Outer"):
for j in tqdm(range(5), desc="Inner"): # leave=True by default
time.sleep(0.05)
```
✅ GOOD: Clean nested bars and scaled units
```python
for i in tqdm(range(3), desc="Outer Loop"):
for j in tqdm(range(5), desc="Inner Loop", leave=False): # Bar disappears after inner loop finishes
time.sleep(0.05)
# Example with units
with tqdm(total=100_000_000, unit="B", unit_scale=True, dynamic_ncols=True, desc="Transferring") as pbar:
for _ in range(100):
pbar.update(1_000_000)
time.sleep(0.01)
```
## 7. Integrate with Data Science Libraries
`tqdm.pandas()` and `tqdm.auto` provide seamless integration for common data science workflows.
### Pandas DataFrames
Use `tqdm.pandas()` to add a `.progress_apply()` method to DataFrames.
```python
import pandas as pd
from tqdm.auto import tqdm # Use tqdm.auto for notebook compatibility
tqdm.pandas() # Register tqdm with pandas
df = pd.DataFrame({'col': range(100000)})
# Applying a function with a progress bar
df['new_col'] = df['col'].progress_apply(lambda x: x * 2)
```
### Jupyter Notebooks
`tqdm.auto` automatically detects if it's running in a Jupyter environment and uses the appropriate display.
```python
# In a .py file that might be run in a notebook or terminal
from tqdm.auto import tqdm, trange
def long_running_task():
for i in trange(100, desc="Auto-detected progress"):
time.sleep(0.01)
long_running_task()
```
## 8. Performance Considerations
`tqdm` is highly optimized, but for extremely fast loops (e.g., millions of iterations per second), consider batching updates or disabling the bar if not strictly necessary.
```python
# For loops where each iteration is extremely fast
total_iterations = 10_000_000
batch_size = 1000
with tqdm(total=total_iterations, desc="Batch Processing") as pbar:
for i in range(0, total_iterations, batch_size):
# Perform batch_size operations here
# ...
pbar.update(batch_size) # Update once per batch
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc