Describe the bug
I just upgraded my API, and the schema changed from 3.0 to 3.1. The uploads suddenly broke. It seems that the code generated for the body class now looks like this:
@define
class File:
"""Contains information for file uploads"""
payload: BinaryIO
file_name: str | None = None
mime_type: str | None = None
def to_tuple(self) -> FileTypes:
"""Return a tuple representation that httpx will accept for multipart/form-data"""
return self.file_name, self.payload, self.mime_type
OpenAPI Spec File
A link to an OpenAPI document which produces this issue. Ideally, write a minimal reproduction only containing the problematic pieces.
openapi-python-client version [e.g. 0.1.0]: 0.28.3
Additional context
I have some code pre-massaging around a different name-related issue and adding this to the pre-processing before this tool is called fixed it, but it's definitely more of a temporary workaround type of thing:
for prop in props.values():
if prop.get("contentMediaType") == "application/octet-stream":
prop["format"] = "binary"
FYI for anyone blocked on this in the meantime: contentMediaType isn't parsed into the internal Schema model at all (confirmed against 0.29.0's openapi_python_client/schema/openapi_schema_pydantic/schema.py), so even bumping to the latest release won't help — this isn't a stale-pin problem.
We worked around it without depending on a fork by patching a throwaway copy of the OpenAPI document right before calling generate(): rewrite each {"type": "string", "contentMediaType": ...} binary marker into the OpenAPI 3.0-style {"type": "string", "format": "binary"} shape the generator already understands (recursing into anyOf/oneOf branches too, for Optional[UploadFile]-style fields). That's enough to get a real types.File-backed multipart field out of an unmodified, released version — no fork, no unreviewed branch dependency in the release pipeline. Roughly:
```python
def _patch_property(prop: dict) -> dict:
if prop.get("type") == "string" and "contentMediaType" in prop:
patched = {k: v for k, v in prop.items() if k != "contentMediaType"}
patched["format"] = "binary"
return patched
for union_key in ("anyOf", "oneOf"):
branches = prop.get(union_key)
if isinstance(branches, list):
patched = dict(prop)
patched[union_key] = [_patch_property(b) for b in branches]
return patched
return prop
def patch_binary_upload_fields(schema: dict) -> dict:
patched = copy.deepcopy(schema)
for definition in patched.get("components", {}).get("schemas", {}).values():
for name, prop in definition.get("properties", {}).items():
definition["properties"][name] = _patch_property(prop)
return patched
```
Run this against a copy of the spec, feed that copy's path to generate --path, never touch the original. It's obviously a stopgap versus a real fix in the parser (#1418 looks like the right shape for that), but it unblocks consumers without anyone needing to trust an unreviewed fork for a release pipeline.
Activity
stephencattaneo commented on May 8, 2026
Got hit by this issue this week. I believe #1418 would fix the issue.
MrSampson commented on Jul 18, 2026
FYI for anyone blocked on this in the meantime: contentMediaType isn't parsed into the internal Schema model at all (confirmed against 0.29.0's openapi_python_client/schema/openapi_schema_pydantic/schema.py), so even bumping to the latest release won't help — this isn't a stale-pin problem.
We worked around it without depending on a fork by patching a throwaway copy of the OpenAPI document right before calling generate(): rewrite each {"type": "string", "contentMediaType": ...} binary marker into the OpenAPI 3.0-style {"type": "string", "format": "binary"} shape the generator already understands (recursing into anyOf/oneOf branches too, for Optional[UploadFile]-style fields). That's enough to get a real types.File-backed multipart field out of an unmodified, released version — no fork, no unreviewed branch dependency in the release pipeline. Roughly:
```python
def _patch_property(prop: dict) -> dict:
if prop.get("type") == "string" and "contentMediaType" in prop:
patched = {k: v for k, v in prop.items() if k != "contentMediaType"}
patched["format"] = "binary"
return patched
for union_key in ("anyOf", "oneOf"):
branches = prop.get(union_key)
if isinstance(branches, list):
patched = dict(prop)
patched[union_key] = [_patch_property(b) for b in branches]
return patched
return prop
def patch_binary_upload_fields(schema: dict) -> dict:
patched = copy.deepcopy(schema)
for definition in patched.get("components", {}).get("schemas", {}).values():
for name, prop in definition.get("properties", {}).items():
definition["properties"][name] = _patch_property(prop)
return patched
```
Run this against a copy of the spec, feed that copy's path to generate --path, never touch the original. It's obviously a stopgap versus a real fix in the parser (#1418 looks like the right shape for that), but it unblocks consumers without anyone needing to trust an unreviewed fork for a release pipeline.