diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcbd6f1b..5cb652cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/instructor/cli/files.py b/instructor/cli/files.py index c83d8929d..976c0c836 100644 --- a/instructor/cli/files.py +++ b/instructor/cli/files.py @@ -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 @@ -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: diff --git a/tests/cli/test_files.py b/tests/cli/test_files.py new file mode 100644 index 000000000..868934f4b --- /dev/null +++ b/tests/cli/test_files.py @@ -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"]