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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
## [Unreleased]

### Fixed
- **CLI files**: `files list` and `files upload` no longer crash with `TypeError: 'FileObject' object is not subscriptable` — `generate_file_table` and `upload` now read the OpenAI SDK's `FileObject` (a Pydantic model) via attribute access instead of dict-style subscripting.
- **v2 message handling**: Preserve caller-owned message lists and nested content across request preparation and retries for OpenAI-compatible, Cohere, Mistral, OpenRouter, Writer, and xAI handlers. ([#2417](https://github.com/567-labs/instructor/issues/2417), [#2428](https://github.com/567-labs/instructor/issues/2428))
- **v2 JSON extraction**: Prefer the final complete top-level JSON value in text responses and retain every JSON object when multiple objects arrive in one streaming chunk.
- **v2 schemas**: Treat fields with Pydantic `default_factory` values as optional in generated OpenAI tool schemas.
Expand Down
12 changes: 6 additions & 6 deletions instructor/cli/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ def generate_file_table(files: list[openai.types.FileObject]) -> Table:

for file in files:
table.add_row(
file["id"],
str(file["bytes"]),
str(datetime.fromtimestamp(file["created_at"])),
file["filename"],
file["purpose"],
file.id,
str(file.bytes),
str(datetime.fromtimestamp(file.created_at)),
file.filename,
file.purpose,
)

return table
Expand Down Expand Up @@ -61,7 +61,7 @@ def upload(
file_purpose = cast(Literal["fine-tune", "assistants"], purpose)
with open(filepath, "rb") as file:
response = client.files.create(file=file, purpose=file_purpose)
file_id = response["id"]
file_id = response.id
with console.status(f"Monitoring upload: {file_id}...") as status:
status.spinner_style = "dots"
while True:
Expand Down
29 changes: 29 additions & 0 deletions tests/cli/test_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Tests for instructor.cli.files."""

import os

os.environ.setdefault("OPENAI_API_KEY", "test-key-for-cli-import")

from openai.types import FileObject

from instructor.cli.files import generate_file_table


def test_generate_file_table_uses_attribute_access():
"""FileObject (and other OpenAI SDK response objects) are Pydantic
models, not dicts — generate_file_table must read fields via attribute
access, not `file["id"]`-style subscripting, which raises TypeError."""
file = FileObject(
id="file-abc123",
bytes=1024,
created_at=1700000000,
filename="training.jsonl",
object="file",
purpose="fine-tune",
status="processed",
)

table = generate_file_table([file])

rendered_first_column = table.columns[0]._cells
assert list(rendered_first_column) == ["file-abc123"]