Document that file objects cannot be iterated

Monty 0.0.19 supports open() and with blocks, but a file object is still not
iterable. See pydantic/monty#490, which is still open. The instructions now
state the limitation in three places and give the alternative next to each
one: readlines() or read().split("\n").

Replace chr(10) with "\n" in the prose and in the example. Both work on
0.0.19, and chr(10) implies that the escape is broken.

Add a test that pins the limitation. The test fails when Monty gains
iteration support, which is the signal to relax the instructions.
This commit is contained in:
Yiorgis Gozadinos 2026-07-28 15:22:27 +03:00
parent 5c8df37af1
commit feaca386d3
No known key found for this signature in database
2 changed files with 48 additions and 4 deletions

View file

@ -17,7 +17,7 @@ Inside the code, these functions are available (use `await`):
- `await list_documents()` → list of dicts with keys: id, title, uri, created_at
Available modules: `json`, `re`, `math`, `pathlib`
Not supported: class definitions, generators/yield, match statements, decorators, `collections`
Not supported: class definitions, generators/yield, match statements, decorators, `collections`, iterating a file object (`for line in f`)
### analysis_search
Search the knowledge base directly (outside code execution). Each result has a `Type:` (paragraph, table, code, list_item, picture). When the Type is `picture`, the corresponding figure may also be attached to the tool response as an image alongside the text — use it directly to answer questions about figures, diagrams, charts, screenshots.
@ -48,7 +48,7 @@ All documents are mounted as a virtual filesystem at `/documents/`:
`{document_id}` is an internal identifier, not the user-facing `uri` (filename, URL, etc.). When you only know a document by its URI or title, use `await list_documents()` to enumerate ids and match against `uri` / `title` — that's a single call to the host. Iterating `/documents/` and reading every `metadata.json` works too but is much slower on portal-scale corpora.
### Reading files
Read with `Path.read_text()` or `open()` (including `with` blocks); file objects support `.read()`, `.readline()`, and `.readlines()`. File objects are NOT iterable — do not write `for line in f`; use `.readlines()` or `text.split(chr(10))` for line-wise processing. Files are read-only; writing raises `PermissionError`.
Read with `Path.read_text()` or `open()` (including `with` blocks); file objects support `.read()`, `.readline()`, and `.readlines()`. A file object cannot be iterated, so read line-wise with `.readlines()` or `.read().split("\n")` instead of `for line in f`. Files are read-only; writing raises `PermissionError`.
```python
from pathlib import Path
@ -63,7 +63,7 @@ for doc_dir in Path('/documents').iterdir():
content = Path(f'/documents/{doc_id}/content.txt').read_text()
# Read and parse items
for line in Path(f'/documents/{doc_id}/items.jsonl').read_text().strip().split(chr(10)):
for line in Path(f'/documents/{doc_id}/items.jsonl').read_text().strip().split("\n"):
item = json.loads(line)
if item['label'] == 'table':
print(item['text'][:200])
@ -112,6 +112,6 @@ You MUST call `analysis_cite` with at least one chunk ID before producing your f
- Use `print()` to output results — the output is your only feedback
- When you write code, execute it — don't describe what code would do. But not every question needs code; simple lookups are best answered by `analysis_search → analysis_cite`.
- Use `await` for all async functions inside `analysis_execute_code` (`search`, `list_documents`)
- Read files with `Path.read_text()` or `open()`/`with`; file objects are not iterable (no `for line in f`) and the `collections` module is unavailable
- Read files with `Path.read_text()` or `open()`/`with`. For lines use `.readlines()` or `.read().split("\n")`, never `for line in f`. The `collections` module is unavailable.
- Do NOT include chunk IDs or UUIDs in your answer text — your answer should read naturally. Use the `analysis_cite` tool separately to register citations. `cite{...}` markdown-style inline references do nothing; only an actual `analysis_cite` tool call registers a citation.
- **Before you write your final answer, invoke the `analysis_cite` tool with the supporting chunk_ids.** This is the last tool call before answering whenever your answer draws on retrieved evidence.

View file

@ -7,6 +7,7 @@ import pytest
from haiku.rag.client import HaikuRAG
from haiku.rag.config.models import AppConfig
from haiku.rag.sandbox import AnalysisContext, Sandbox, SandboxResult
from haiku.rag.store.models.chunk import Chunk
@pytest.fixture(scope="module")
@ -454,6 +455,49 @@ class TestSandboxVFS:
assert "DENIED" in result.stdout
assert "WROTE" not in result.stdout
@pytest.mark.asyncio
async def test_open_file_objects_are_not_iterable(self, temp_db_path):
"""Pins the limitation the instructions warn about: pydantic/monty#490.
A failure here means Monty gained iteration support and the
`for line in f` prohibition in the analysis instructions is now wrong.
"""
from docling_core.types.doc.document import DoclingDocument
from docling_core.types.doc.labels import DocItemLabel
config = AppConfig()
docling = DoclingDocument(name="d")
docling.add_text(label=DocItemLabel.TEXT, text="one\ntwo")
async with HaikuRAG(temp_db_path, create=True) as client:
doc = await client.import_document(
docling,
[
Chunk(
content="one\ntwo",
embedding=[0.1] * config.embeddings.model.vector_dim,
order=0,
)
],
uri="test://lines",
)
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
try:
result = await sb.execute(
f"for line in open('/documents/{doc.id}/content.txt'):\n print(line)"
)
assert result.success is False
assert "not iterable" in result.stderr
# The documented alternatives do work.
result = await sb.execute(
f"print(len(open('/documents/{doc.id}/content.txt').readlines()))"
)
assert result.success, result.stderr
finally:
await sb.close()
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_context_filter_limits_vfs(self, temp_db_path):