Skip to content

feat(tables): TablesAPI.upload for /v1/tables/upload/#40

Closed
jadenfix wants to merge 4 commits into
mainfrom
codex/public-upload-table-sdk
Closed

feat(tables): TablesAPI.upload for /v1/tables/upload/#40
jadenfix wants to merge 4 commits into
mainfrom
codex/public-upload-table-sdk

Conversation

@jadenfix
Copy link
Copy Markdown
Member

Summary

  • New client.tables.upload(table_name, file, with_headers=True, organization_id=None) API.
  • file accepts str | Path | bytes | BinaryIO | FileUpload; mime-guesses to text/csv for .csv names.
  • Regenerated _generated/api/tables/upload_table.py + TableUploadRequest / TableUploadResponse models from the backend openapi.
  • Wired into RoeClient.tables property + module __init__ exports.

Backend PR: https://github.com/roe-ai/roe-main/pull/3245

Test Plan

  • tests/unit/test_tables.py — 2 unit tests passing (bytes path multipart shape + RoeClient property wiring).
  • Live upload against the dev stack with /Users/jadenfix/secondary_agent_results_v2.csv returns 201 with 7 written rows; confirmed in ClickHouse.
  • Existing tests still pass (uv run pytest tests/).

🤖 Generated with Claude Code

…point

Adds a `client.tables.upload(table_name, file, with_headers=True)`
helper that wraps the new backend public endpoint. Accepts str | Path
| bytes | BinaryIO | FileUpload for the file argument, defaults to
the configured organization, and returns a typed TableUploadResponse.

Regenerated openapi-python-client artifacts under _generated/ for the
new operation + request/response schemas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR adds TablesAPI.upload() — a new public method that accepts a CSV file in several forms (str | Path | bytes | BinaryIO | FileUpload) and POSTs it to the new /v1/tables/upload/ endpoint as a multipart request. It also ships the generated OpenAPI client (upload_table.py, TableUploadRequest, TableUploadResponse) and wires RoeClient.tables as a property.

  • TablesAPI._as_generated_file() correctly handles each input type, guesses the MIME type, and flags which streams to close after the request — file descriptor management is sound.
  • The generated models (table_upload_request.py, table_upload_response.py, upload_table.py) carry several duplicate and unused imports that were flagged in prior review threads and remain unaddressed.
  • to_multipart() in TableUploadRequest has two known serialisation bugs flagged previously: the None org-id branch sends the literal string "None" instead of omitting the field, and the UUID branch is the only one that omits .encode() on its string value.

Confidence Score: 4/5

Safe to merge after addressing the previously flagged to_multipart() serialisation bugs; the TablesAPI wrapper itself guards against both issues at the call site.

The high-level TablesAPI.upload() path is correct: it always passes a real UUID (never None) to TableUploadRequest, so the b"None" multipart bug flagged in the previous thread can only be triggered by callers who construct TableUploadRequest directly. The generated files still carry duplicate/unused import noise and the inconsistent encoding in the UUID branch. The resp.parsed result is not guarded against unexpected 2xx responses, which would silently return None to the caller.

The three generated files (upload_table.py, table_upload_request.py, table_upload_response.py) still need the cleanup from the prior review threads; table_upload_request.to_multipart() in particular has the None-as-UUID serialisation bug for direct consumers of the generated model.

Important Files Changed

Filename Overview
src/roe/api/tables.py New TablesAPI wrapper; file-type coercion and close-on-exit logic are correct; resp.parsed is not guarded against unexpected 2xx returning None
src/roe/_generated/models/table_upload_request.py Generated model; contains duplicate imports, unused imports (BinaryIO, TextIO, TYPE_CHECKING, Generator), and the UUID branch in to_multipart() is missing .encode() unlike all other branches
src/roe/_generated/api/tables/upload_table.py Generated endpoint file; has duplicate from typing import cast imports (lines 4 and 15) and an unused from urllib.parse import quote import
src/roe/_generated/models/table_upload_response.py Generated response model; has duplicate from ..types import UNSET, Unset import and several unused typing imports (BinaryIO, TextIO, TYPE_CHECKING, Generator)
src/roe/client.py Cleanly wires TablesAPI into RoeClient via a cached instance and a typed property
openapi/openapi.yml Adds /v1/tables/upload/ POST endpoint schema with correct request/response shapes; summary field intentionally left without a type (any)

Sequence Diagram

sequenceDiagram
    participant Caller
    participant TablesAPI
    participant _as_generated_file
    participant TableUploadRequest
    participant upload_table
    participant Backend

    Caller->>TablesAPI: upload(table_name, file, ...)
    TablesAPI->>TablesAPI: resolve organization_id → UUID or UNSET
    TablesAPI->>_as_generated_file: coerce file type
    _as_generated_file-->>TablesAPI: File(payload, ...), close_after
    TablesAPI->>TableUploadRequest: build(table_name, file, with_headers, org_id)
    TableUploadRequest->>TableUploadRequest: to_multipart() → RequestFiles
    TablesAPI->>upload_table: sync_detailed(client, body)
    upload_table->>Backend: POST /v1/tables/upload/ (multipart/form-data)
    Backend-->>upload_table: 201 TableUploadResponse / 400 ErrorResponse
    upload_table-->>TablesAPI: "Response[ErrorResponse | TableUploadResponse]"
    TablesAPI->>TablesAPI: translate_response (raise on non-2xx)
    TablesAPI->>TablesAPI: close payload if close_after
    TablesAPI-->>Caller: TableUploadResponse
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "revert(generated): undo manual generated..." | Re-trigger Greptile

Comment thread src/roe/_generated/models/table_upload_request.py
Comment thread src/roe/_generated/api/tables/upload_table.py
Comment thread src/roe/_generated/models/table_upload_response.py
Comment thread src/roe/_generated/models/table_upload_request.py
jadenfix and others added 3 commits May 20, 2026 11:14
Greptile P1 on #40: `to_multipart()` serialized an
explicit None to the literal bytes `b"None"`, which the backend
rejects as an invalid UUID. The hand-written `TablesAPI.upload`
wrapper never passes None at runtime (it coerces to the configured
org id), but anyone calling the generated layer directly would hit it.

Manual patch to the generated file — kept narrow so the next
openapi-python-client regen produces a clean diff. The duplicate /
unused imports Greptile also flagged in the same generated files
(P2) are left alone for the same reason; they'll be cleaned up
when codegen is re-run upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile P1 on #40: TableUploadRequest's multipart
serializer turns an explicit `organization_id=None` into the literal
bytes `b"None"`, which the backend rejects as an invalid UUID.

The first attempt patched the generated multipart serializer directly,
but `check-codegen-drift` (correctly) blocks divergence between
checked-in generated code and what openapi-python-client produces.
Move the fix to the hand-written wrapper instead: coerce to UUID when
a value is available, otherwise pass UNSET so the generated form-field
serializer skips the field cleanly.

Re-validated against the live dev stack with the canonical CSV.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…apper

The earlier `fix(generated)` commit patched openapi-python-client output
to handle organization_id=None. That diverged from what the codegen
would produce and tripped `check-codegen-drift`. The wrapper now passes
UNSET instead of None (previous commit), so the generated layer no
longer needs to special-case None — restore it to the canonical
openapi-python-client output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jadenfix
Copy link
Copy Markdown
Member Author

@greptile review

@jadenfix
Copy link
Copy Markdown
Member Author

Closing per request.

@jadenfix jadenfix closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant