feat(api): update API spec from langfuse/langfuse a6c38c6#1574
Conversation
|
@claude review |
| export_start_date: typing_extensions.Annotated[ | ||
| typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") | ||
| ] = None | ||
| compressed: bool |
There was a problem hiding this comment.
🔴 is declared as a required field () with no default value, meaning any older self-hosted Langfuse server that does not yet return this field in API responses will cause Pydantic to raise a , crashing and . Fix by changing to to match the server-side default and maintain backward compatibility.
Extended reasoning...
The Bug
at line 43 declares — a required Pydantic field with no default. Pydantic v2 raises a if a required field is absent from the parsed data. There is no mechanism in or the setting that compensates for missing required fields — only permits unexpected extra keys, not absent required ones.
Code Path That Triggers It
Both and ultimately call in . If the JSON response from the server lacks the key, this call raises .
Why Existing Code Does Not Prevent It
The setting is insufficient. means Pydantic tolerates extra keys beyond what the model declares — it does not provide defaults for absent declared fields. does not override or validators to fill in missing required fields. The only way to make a Pydantic field tolerant of absence is a Python-level default (, , etc.).
Impact
The Langfuse Python SDK is used against both langfuse.com (cloud) and self-hosted instances. Self-hosted users on older server versions — those deployed before the feature was added — will receive API responses without the key. Every call to list or upsert blob storage integrations will crash with an unhandled . This is a hard failure for anyone running an older self-hosted Langfuse server with a newer SDK.
Addressing the Refutation
The refutation argues this is auto-generated from an API spec where the server contract guarantees will always be present. This holds for the current server version, but Langfuse is self-hostable and users frequently run older instances. A new SDK talking to an older server is a completely normal scenario. The SDK own established pattern — used for , , , , and — is to give defaults to fields added over time. The same principle applies here. Furthermore, and were always part of the API; is genuinely new and will be absent from older server responses.
Step-by-Step Proof
- User runs self-hosted Langfuse server at version X (predating the field addition).
- User upgrades their Python SDK to the version containing this PR.
- User calls .
- Server returns JSON without : .
- SDK calls , constructing objects.
- Pydantic raises: .
- Exception propagates; the SDK call fails entirely.
Fix: Change to in . This matches the server-side default and is consistent with how other recently-added fields in the same model are handled.
Disclaimer: Experimental PR review
Greptile Summary
This auto-generated PR (via Fern from the Langfuse API spec at commit
a6c38c6) adds acompressedfield to the blob storage integration API surface, enabling gzip compression for exported files (.csv.gz,.json.gz,.jsonl.gz).Changes:
BlobStorageIntegrationResponse: addscompressed: boolas a required field — the server is now expected to always return this value in responses.CreateBlobStorageIntegrationRequest: addscompressed: Optional[bool] = Noneso callers may omit it and rely on the server-side default oftrue.client.py/raw_client.py(sync + async): threads the newcompressed: Optional[bool] = OMITparameter through the public and raw client method signatures and correctly includes it in the serialized request body dict, where it is dropped from the payload when left asOMIT.Confidence Score: 5/5
compressedfield is added consistently and correctly across the response type, request type, public client, and raw client layers, following the exact same patterns used for every other optional parameter in this module. No imports were added inside functions or methods, so the custom import-ordering rule is satisfied. There are no observable logic or security issues.Important Files Changed
compressed: boolas a required (non-optional) field to the response model, matching the API spec where the server always returns this value.compressed: Optional[bool] = Noneto the request model; optional so callers can omit it and let the server apply its default oftrue.compressed: Optional[bool] = OMITparameter to bothBlobStorageIntegrationsClientandAsyncBlobStorageIntegrationsClientcreate/update methods, forwarding it to the raw client.compressed: Optional[bool] = OMITto both sync and async raw client methods and correctly includes"compressed": compressedin the request body dict, which is filtered byomit=OMIT.Sequence Diagram
sequenceDiagram participant Caller participant BlobStorageIntegrationsClient participant RawBlobStorageIntegrationsClient participant LangfuseAPI Caller->>BlobStorageIntegrationsClient: create_or_update(..., compressed=True) BlobStorageIntegrationsClient->>RawBlobStorageIntegrationsClient: create_or_update(..., compressed=True) RawBlobStorageIntegrationsClient->>LangfuseAPI: POST /api/blob-storage-integrations\n{..., "compressed": true} LangfuseAPI-->>RawBlobStorageIntegrationsClient: BlobStorageIntegrationResponse\n{..., "compressed": true} RawBlobStorageIntegrationsClient-->>BlobStorageIntegrationsClient: HttpResponse[BlobStorageIntegrationResponse] BlobStorageIntegrationsClient-->>Caller: BlobStorageIntegrationResponseReviews (1): Last reviewed commit: "feat(api): update API spec from langfuse..." | Re-trigger Greptile