Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions .hermes/plans/2026-06-06_103000-fsize-issue-3-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# FSize __format__ empty spec fix Implementation Plan

> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.

**Goal:** Fix issue #3: format(fsize, "") should equal str(fsize) by returning str(self) when format_spec is empty.

**Architecture:** Add an early guard clause in FSize.__format__ to handle empty format_spec, preserving existing behavior for non-empty specs.

**Tech Stack:** Python, pytest.

---

### Task 1: Write failing test for empty format spec

**Objective:** Add a test that verifies format(x, "") == str(x) for various FSize values.

**Files:**
- Create: (none)
- Modify: tests/test_fsize_init.py: (add test function)
- Test: tests/test_fsize_init.py

**Step 1: Write failing test**

```python
def test_format_empty_spec():
"""Test that format with empty string equals str."""
x = FSize(1024)
assert format(x, "") == str(x)
assert f"{x}" == str(x) # f-string with no spec
# Test with different units
y = FSize(1, "MiB")
assert format(y, "") == str(y)
z = FSize(1, "KB")
assert format(z, "") == str(z)
```

**Step 2: Run test to verify failure**

Run: .venv/bin/pytest tests/test_fsize_init.py::test_format_empty_spec -v

Expected: FAIL — because format(x, "") currently returns K-unit value.

**Step 3: Write minimal implementation**

Modify src/fsize/__init__.py in __format__ method:

Add at the very beginning of the method:

```python
def __format__(self, format_spec: str) -> str:
"""Format the FSize value."""
if not format_spec:
return str(self)
# ... rest of existing code unchanged
```

**Step 4: Run test to verify pass**

Run: .venv/bin/pytest tests/test_fsize_init.py::test_format_empty_spec -v

Expected: PASS

**Step 5: Commit**

```bash
git add src/fsize/__init__.py tests/test_fsize_init.py
git commit -m "fix: handle empty format spec in __format__"
```

### Task 2: Run full test suite to ensure no regressions

**Objective:** Ensure all existing tests still pass after the change.

**Files:**
- Test: run full pytest

**Step 1: Run all tests**

Run: .venv/bin/pytest -v

Expected: all tests pass

**Step 2: Commit if any changes (should be none)**

If any test fails, investigate and fix.

### Task 3: Verify with mypy and pylint

**Objective:** Ensure code passes type checking and linting.

**Files:**
- Check: run mypy and pylint

**Step 1: Run mypy**

Run: .venv/bin/mypy .

Expected: no errors

**Step 2: Run pylint**

Run: .venv/bin/pylint .

Expected: no errors (or only existing ones? but we assume none)

**Step 3: Commit if any fixes needed**

If errors, fix them and commit.

### Task 4: Update documentation if needed (optional)

**Objective:** Ensure any docstrings or comments are accurate.

**Files:**
- Possibly modify src/fsize/__init__.py docstring for __format__ to mention empty spec behavior.

But the existing docstring says default is "K". We might want to note that empty spec returns str(self). However, the issue description says Python's data model requires format(x, "") == str(x). We can update docstring.

We'll add a note.

**Step 1: Update docstring**

In __format__ docstring, add a line: "An empty format_spec returns str(self) as per Python's data model."

**Step 2: Commit**

```bash
git add src/fsize/__init__.py
git commit -m "doc: clarify __format__ behavior for empty spec"
```

### Task 5: Final verification

Run full test suite, mypy, pylint one more time.

Then consider the task complete.
2 changes: 1 addition & 1 deletion src/fsize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def __format__(self, format_spec: str) -> str:
raise AssertionError(f"unhandled unit: {unit!r}")
n = self.real / self._convert ** _UNIT_POWERS[unit]

log_digits = math.ceil(math.log10(n)) if n > 0 else 0
log_digits = math.floor(math.log10(n)) + 1 if n > 0 else 0
out_format_spec = (
f"{fill}{align}{width}{grouping}"
+ "."
Expand Down
7 changes: 7 additions & 0 deletions tests/test_fsize_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,13 @@ def test_format_empty_spec():
assert format(z, "") == str(z)


def test_format_no_scientific_notation():
"""Test that exact powers of 10 do not produce scientific notation."""
assert format(FSize(10 * 1024), "K") == "10"
assert format(FSize(100 * 1024), "K") == "100"
assert format(FSize(1000 * 1024), "K") == "1000"


def test_format_all_units():
"""Test that every unit in _UNIT_POWERS works via format."""
# Binary FSize (1 EiB)
Expand Down
Loading