Skip to content

[python] add typeddict models-mode for Python HTTP client emitter#10439

Open
iscai-msft wants to merge 58 commits into
microsoft:mainfrom
iscai-msft:python/addTypedDict
Open

[python] add typeddict models-mode for Python HTTP client emitter#10439
iscai-msft wants to merge 58 commits into
microsoft:mainfrom
iscai-msft:python/addTypedDict

Conversation

@iscai-msft
Copy link
Copy Markdown
Member

@iscai-msft iscai-msft commented Apr 21, 2026

fixes #8800

Add a new 'typeddict' value for the models-mode option that generates Python TypedDict classes instead of DPG model classes. Key features:

  • TypedDict classes with Required[T]/NotRequired[T] annotations
  • TypedDict inheritance for non-discriminated models
  • Discriminated models: Union of leaf TypedDicts, no abstract base class
  • Input-only: operations accept TypedDict input, return dict output
  • Wire names used as TypedDict keys
  • _model_base.py still generated for serialization utilities

Add a new 'typeddict' value for the models-mode option that generates
Python TypedDict classes instead of DPG model classes. Key features:

- TypedDict classes with Required[T]/NotRequired[T] annotations
- TypedDict inheritance for non-discriminated models
- Discriminated models: Union of leaf TypedDicts, no abstract base class
- Input-only: operations accept TypedDict input, return dict output
- Wire names used as TypedDict keys
- _model_base.py still generated for serialization utilities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:python Issue for the Python client emitter: @typespec/http-client-python label Apr 21, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-python@10439

commit: 7f2ffd5

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

All changed packages have been documented.

  • @typespec/http-client-python
Show changes

@typespec/http-client-python - feature ✏️

[python] Always generate TypedDict typing hints for input models in the types.py file, and named union aliases in the _unions.py file

@azure-sdk
Copy link
Copy Markdown
Collaborator

azure-sdk commented Apr 21, 2026

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

iscai-msft and others added 12 commits April 21, 2026 14:37
- TypedDictModelType returns 'JSON' for response type annotations
- Response.type_annotation/docstring passes is_response=True
- Typeddict deserialization uses response.json() directly
- Removed NotRequired from TypedDictModelSerializer (total=False handles it)
- Updated mock API tests to verify JSON dict responses

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add client/naming typeddict variant to regenerate-common.ts
- Create test_client_naming_typeddict.py with 11 tests verifying TypedDict
  uses wire names (defaultName, wireName) not client names
- Tests cover: ClientNameModel, LanguageClientNameModel,
  ClientNameAndJsonEncodedNameModel, ClientModel, PythonModel

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TypedDict is already JSON, so the MutableMapping[str, Any] overload
is unnecessary. Only keep TypedDict model + IO[bytes] overloads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Typeddict mode uses response.json() directly, so _deserialize is never
called. Skip importing it to avoid W0611 unused-import lint warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused MutableMapping/Any imports from TypedDictModelType.imports()
- Skip _deserialize import in paging_operation.py for typeddict mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TypedDictModelType returns 'JSON' for response type annotations but
never defined the JSON = MutableMapping[str, Any] type alias, causing
NameError at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@iscai-msft iscai-msft force-pushed the python/addTypedDict branch from 34e0cda to 95db199 Compare April 29, 2026 15:39
iscai-msft and others added 7 commits May 26, 2026 13:51
- Fix cross-namespace parent imports in types.py to import from types module
- Add isidentifier() check for wire names (not just iskeyword) for functional TypedDict form
- Use __doc__ assignment for functional-form TypedDict docstrings
- Add missing op_tools import in model_typeddict.py.jinja2
- Fix test docstring to match actual behavior
- Update changelog entry with correct file names (types.py, _unions.py)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@iscai-msft iscai-msft requested a review from l0lawrence as a code owner June 1, 2026 16:26
- Fix cross-namespace parent imports in types_serializer.py and
  model_type.py to use get_relative_import_path with module_name
  parameter instead of naive string concatenation
- Remove unused model_typeddict.py.jinja2 template (dead code)
- Fix misleading test name: rename test_models_mode_dpg_no_typeddict_models
  to test_models_mode_dpg_typeddict_models_included

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
: property.serializationOptions?.json?.name) ?? property.name,
type: getType(context, sourceType),
optional: property.optional,
nullable: isNullable,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are conflating nullable and optional. this is one step to help with the conflation, since it's extra important in typeddicts to not type things as optional when we mean nullable

Revert to the original no-sdk-clients check without tying the
exemption to models-mode=typeddict. Models-only generation should
not be gated on a specific models-mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
def type_annotation(self, **kwargs: Any) -> str:
if self.name:
return f'"_types.{self.name}"'
return f'"_unions.{self.name}"'
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the existing union types to a unions.py file, keeping types.py purely for typeddict generation. So now, really instead of using JSON, we will be able to use the typeddict representations instead for those overloads. If we want to as well, we can not generate models and only generate typeddicts as well

"""
if self.code_model.options["models-mode"]:
if self.is_typeddict_mode:
# In typeddict mode, enums are Literal aliases defined in types.py
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatted with @johanste and in TD only mode, we don't want to generate enums as enum types, but instead as unions of literals. @johanste is this something we want to do everywhere?

self.cross_language_definition_id: Optional[str] = self.yaml_data.get("crossLanguageDefinitionId")
self.usage: int = self.yaml_data.get("usage", UsageFlags.Input.value | UsageFlags.Output.value)
self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)
self.is_typed_dict_only: bool = (
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to be able to flag individual models as typeddict only as well. we will need to add a decorator in tcgc in order to support this

Comment thread packages/http-client-python/eng/scripts/ci/regenerate-common.ts
Comment thread packages/http-client-python/emitter/src/types.ts
iscai-msft and others added 4 commits June 2, 2026 13:48
Replace the JSON (MutableMapping) overload with a TypedDict reference
from types.py for model input parameters. This gives users proper
type-checking and IDE completion when passing dict literals.

Changes:
- preprocess: insert TypedDict model reference instead of any-object
  for both model and list/dict body params with DPG model elements
- TypedDictModelType: add type_annotation, imports, docstring_type,
  and instance_check_template overrides
- build_type: handle base='typeddict' to create TypedDictModelType
- code_model: exclude typeddict refs from model_types (annotation-only)

Generated operations now show:
  @overload def op(body: _models.X, ...) -> ...
  @overload def op(body: types.X, ...) -> ...
  @overload def op(body: IO[bytes], ...) -> ...

JSON overloads are preserved for spread bodies (base=json) where
no model class exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file is shared with typespec-python via pnpm sync and
should not be deleted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@msyyc
Copy link
Copy Markdown
Contributor

msyyc commented Jun 4, 2026

I invoked workflow to generate code diff for this PR: Azure/azure-sdk-for-python#47346 for better review.

iscai-msft and others added 2 commits June 4, 2026 15:19
When multiple namespaces import 'types', the second import shadows the
first causing mypy errors ('Name types already defined') and runtime
failures ('Name types.X is not defined').

Add get_unique_types_alias() (mirrors get_unique_models_alias) to
generate unique aliases like _types, _types_models1, _types_models2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@iscai-msft iscai-msft force-pushed the python/addTypedDict branch from d6eb5ab to 4c0a3ac Compare June 4, 2026 20:09
In types.py (TypedDict definitions), use the JSON wire type instead of
rich Python types for properties:
- datetime.datetime → str
- datetime.date → str
- datetime.time → str
- datetime.timedelta → str
- datetime.datetime (unix) → int
- decimal.Decimal → float
- bytes → str

This follows the principle that TypedDicts represent the wire/JSON shape
of the data. Rich types like datetime are still used in model classes
and operation signatures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@iscai-msft iscai-msft requested review from johanste and msyyc June 5, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:python Issue for the Python client emitter: @typespec/http-client-python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Python] Add alpha TypedDict support

5 participants