# 9.2: gRPC `upsert_from_dataframe` reports partial failures instead of raising In 9.1 and earlier, `GrpcIndex.upsert_from_dataframe` raised as soon as any batch failed. From 9.2 it aggregates, matching `upsert(batch_size=...)` and the REST transport, which has behaved this way since v9.0.0. ## What changed ```python # 9.1 and earlier — gRPC try: index.upsert_from_dataframe(df) except Exception: # No way to tell how much landed. Re-run the whole frame. ... # 9.2 — gRPC and REST response = index.upsert_from_dataframe(df) if response.failed_item_count: for error in response.errors: print(error.error_message) index.upsert_from_dataframe(pd.DataFrame(response.failed_items)) ``` `UpsertResponse` already carried `upserted_count`, `failed_item_count`, `errors` and `failed_items`, so there is no new model to learn — the gRPC transport simply starts populating them. ## If you depended on the raise Pass `on_error="raise"`: ```python index.upsert_from_dataframe(df, on_error="raise") ``` That re-raises the lowest-indexed batch failure, which is what 9.1 did, with two improvements: - every batch settles before the exception propagates, so nothing is left running server-side; - the partial result is attached to the exception, so the count is no longer lost: ```python try: index.upsert_from_dataframe(df, on_error="raise") except Exception as exc: print(exc.response.upserted_count) retry_these = exc.response.failed_items ``` `on_error` is a posture choice, not a migration shim — fail-fast on ingest is legitimate, and it is not going away. It is available on both transports. ## Why this is a break we chose to take Both behaviors were already public and depended on: REST has aggregated since v9.0.0, gRPC raised since forever. Partial failure either raises or it does not, so consistency and no-breaking-change were in genuine conflict. What this gives up is the *silence*, not the consistency. The blast radius is also smaller than it looks: the old raise discarded the partial count, so no caller relying on it could tell what had landed — a careful gRPC caller was already re-running the whole frame, which is safe either way because upserts are idempotent by vector ID. ## Finding out at runtime The first time a gRPC ingest hits a partial failure without an explicit `on_error`, the SDK warns once per process, naming `response.errors` and `on_error="raise"`. Passing `on_error` explicitly — either value — silences it. REST does not warn: its behavior did not change. ## Related - `total_timeout` (new in 9.2) follows `on_error` the same way: under `"collect"` an expired deadline returns the partial response, under `"raise"` it raises `PineconeTimeoutError` carrying the same partial result.