Skip to content

typespec-python emitter generates empty class for Record<string> inside MergePatchUpdate<T> #3358

@yungshinlintw

Description

@yungshinlintw

Bug Description

When a TypeSpec model containing a Record<string> property is wrapped with MergePatchUpdate<T>, the Python emitter generates an empty, unusable class RecordMergePatchUpdate instead of emitting Dict[str, str].

TypeSpec Source

The ContentUnderstandingDefaults model has a Record<string> property:

model ContentUnderstandingDefaults {
  modelDeployments: Record<string>;
}

This model is used in an operation via MergePatchUpdate<T>:

updateDefaults is Foundations.Operation<
  MergePatchUpdate<ContentUnderstandingDefaults>,
  ContentUnderstandingDefaults,
  ServiceTraits
>;

Generated Code (Broken)

The emitter generates an empty class with no properties, constructor, or dict-like behavior:

class RecordMergePatchUpdate(_Model):
    """RecordMergePatchUpdate."""

This type is then used in the generated update_defaults operation signature:

def update_defaults(
    self,
    model_deployments: Optional[RecordMergePatchUpdate] = None,
    ...
)

User Impact

Users cannot pass a dictionary to model_deployments. The empty class has no way to set key-value pairs, making the update_defaults API effectively broken without a workaround.

Contrast: Same Record<string> Works Correctly Elsewhere

The same Record<string> type on ContentUnderstandingDefaults (the response model, not the merge-patch input) generates correctly as dict[str, str]:

class ContentUnderstandingDefaults(_Model):
    model_deployments: dict[str, str] = rest_field(name="modelDeployments", ...)

Other Record<string> properties (e.g., tags on ContentAnalyzer) also generate correctly as dict[str, str]. The issue is specifically triggered when MergePatchUpdate<T> wraps a model containing Record<string>.

Our Workaround

We patched it in _patch.py by aliasing the empty class to Dict[str, str] and monkey-patching it onto the models module at runtime:

# _patch.py
RecordMergePatchUpdate = Dict[str, str]

def patch_sdk():
    from . import _models
    _models.RecordMergePatchUpdate = RecordMergePatchUpdate

Expected Behavior

The emitter should either:

  1. Generate RecordMergePatchUpdate as a type alias for Dict[str, str], or
  2. Inline the Record<string> property on the merge-patch model as dict[str, str] (same as it does for the non-patch model)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions