Skip to content

Add an opt-in value view for WinRT Object-valued maps in Python - #195

Merged
leileizhang (lei9444) merged 7 commits into
lei9444-explicit-python-object-boxingfrom
lei9444-python-object-map-value-view
Sep 28, 2026
Merged

leileizhang (lei9444) merged 7 commits into
lei9444-explicit-python-object-boxingfrom
lei9444-python-object-map-value-view

Conversation

@lei9444

@lei9444 leileizhang (lei9444) commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Code that reads and writes many values in PropertySet, ValueSet, DeviceInformation.properties or maps returned by retrieve_properties_async() must wrap every access in unbox_object() or to_winrt_object().

Key changes

  • dynwinrt.values.object_value_view(mapping, *, preserve_type=False) returns a live MutableObjectValueView for IMap and a read-only ObjectValueView for IMapView.
  • Reads unbox values; writes use to_winrt_object()'s default rules; .raw remains the original generated map with native values and COM identity.
  • Unsupported PropertyType boxes and out-of-range DateTime boxes read back raw. Supported getter failures still propagate unchanged.
  • Generated runtime map wrappers privately declare the exact map IID and _obj/_collection_obj dispatch path. The view validates that declaration and QI support without assuming non-IUnknown pointer identity.
  • Generic key typing accepts str and UUID, and rejects integer-keyed maps.
  • Tests cover PropertySet, ValueSet, StringMap, MediaPropertySet, device/file properties, dual-map dispatch, strict typing and standard Mapping semantics.

Notes

  • Stacked on Add explicit WinRT Object boxing and type-preserving unboxing to Python #194.
  • Opt-in only; explicit Object conversion behavior is unchanged and JavaScript is untouched.
  • Older generated runtime-class map wrappers fail closed; reproject with the matching IMap/IMapView wrapper.
  • Generated IPropertySet wrappers are not Python mappings; use values.as_interface(IMap_String_Object).
  • JavaScript parity is a follow-up.

dynwinrt.values.object_value_view(mapping, *, preserve_type=False) wraps a
generated IMap or IMapView wrapper whose values are Object, such as
PropertySet, ValueSet or DeviceInformation.properties. It returns a live
MutableObjectValueView or read-only ObjectValueView that holds no WinRT
reference of its own: reads unbox with unbox_object(), writes box with
to_winrt_object()'s default rules, and view.raw is the generated map.

QueryInterface for IMap/IMapView<String or Guid, Object> confirms the value
type, so other maps such as StringMap raise TypeError, and the overloads
reject them statically. A box without a Python form (an unsupported
PropertyType or a DateTime outside datetime's range) reads back raw so that
dict(view) does not fail on one odd entry. Generated code, unbox_object()
and to_winrt_object() are unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
update() writes through __setitem__ and to_winrt_object(), so its overloads
now accept any value, as item assignment already does. They mirror
MutableMapping.update(), which allows keyword arguments only for str keys.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
setdefault() now accepts any default that to_winrt_object() accepts, like
__setitem__ and update(), and returns the read type: after storing a missing
key it reads the value back, so (1, 2) returns [1, 2] and a runtime object
its DynWinRTValue. MutableMapping.setdefault() would return the default
itself. A targeted type: ignore[override] covers typeshed's self-typed
overload, which infers "-> None" for a value type that includes None.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mark only binding-classified unsupported PropertyTypes so a supported
IPropertyValue getter that returns the same HRESULT still propagates. Verify
that a generated wrapper's actual map dispatch pointer is the reflexive
IMap/IMapView<String or Guid, Object> pointer, rejecting another map wrapper
on the same COM identity while preserving .raw. Constrain the public view key
type to str or UUID.

Add a synthetic dual-map COM object, getter-error identity checks, strict
mypy/Pyright key controls, and generated Guid-map E2E coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lei9444
leileizhang (lei9444) merged commit ae7fee8 into lei9444-explicit-python-object-boxing Sep 28, 2026
2 checks passed
@lei9444
leileizhang (lei9444) deleted the lei9444-python-object-map-value-view branch September 28, 2026 14:01
leileizhang (lei9444) added a commit that referenced this pull request Sep 29, 2026
…on (#194)

* Add explicit WinRT Object boxing and type-preserving unboxing to Python

Generated Object positions stay native (IInspectable); applications now
convert boxed values explicitly at the boundaries that want value semantics.

Core: PropertyValueData models every PropertyType that has a payload (37 of
41), unbox_property_value reports payload-less boxes as Unsupported, and
box_property_value creates system PropertyValue boxes. JavaScript keeps its
exact behavior and messages through a compile-only adapter.

Python:
- unbox_object additionally supports DateTime, TimeSpan, Point, Size, Rect,
  their arrays and InspectableArray (elements unboxed by the same rules);
  already-supported types return the same values, and payload-less boxes
  still raise OSError.
- unbox_object(raw, preserve_type=True) returns dynwinrt.values tags and typed
  arrays, so to_winrt_object restores the exact PropertyType and value.
- to_winrt_object(value, property_type=None) boxes only unambiguous values:
  a plain int is Int32 only, lists must be homogeneous, and enum members,
  empty or mixed lists and InspectableArray need an explicit type.
- The tags, typed arrays, Point/Size/Rect and PropertyType live in the new
  dynwinrt.values submodule, outside the top-level namespace.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Cover explicit Object conversion with generated-binding E2E checks

Python-only checks on generated bindings:
- object_value_roundtrip: every PropertyValue.create_* factory (37
  PropertyTypes) round-trips through unbox_object(preserve_type=True) and
  to_winrt_object with its exact PropertyType, and dynwinrt.values.PropertyType
  matches the metadata enum.
- object_value_storage_properties: a temporary file's System.Size unboxes as
  UInt64 in preserve mode and System.DateModified as an aware datetime.
- object_value_device_properties: DeviceInformation.properties values unbox
  as str, bool and UUID, skipping gracefully without devices.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Add an opt-in value view for WinRT Object-valued maps in Python (#195)

* Add an opt-in value view for WinRT Object-valued maps in Python

dynwinrt.values.object_value_view(mapping, *, preserve_type=False) wraps a
generated IMap or IMapView wrapper whose values are Object, such as
PropertySet, ValueSet or DeviceInformation.properties. It returns a live
MutableObjectValueView or read-only ObjectValueView that holds no WinRT
reference of its own: reads unbox with unbox_object(), writes box with
to_winrt_object()'s default rules, and view.raw is the generated map.

QueryInterface for IMap/IMapView<String or Guid, Object> confirms the value
type, so other maps such as StringMap raise TypeError, and the overloads
reject them statically. A box without a Python form (an unsupported
PropertyType or a DateTime outside datetime's range) reads back raw so that
dict(view) does not fail on one odd entry. Generated code, unbox_object()
and to_winrt_object() are unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Type MutableObjectValueView.update() like item assignment

update() writes through __setitem__ and to_winrt_object(), so its overloads
now accept any value, as item assignment already does. They mirror
MutableMapping.update(), which allows keyword arguments only for str keys.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Type MutableObjectValueView.setdefault() like item assignment

setdefault() now accepts any default that to_winrt_object() accepts, like
__setitem__ and update(), and returns the read type: after storing a missing
key it reads the value back, so (1, 2) returns [1, 2] and a runtime object
its DynWinRTValue. MutableMapping.setdefault() would return the default
itself. A targeted type: ignore[override] covers typeshed's self-typed
overload, which infers "-> None" for a value type that includes None.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address Object map view review findings

Mark only binding-classified unsupported PropertyTypes so a supported
IPropertyValue getter that returns the same HRESULT still propagates. Verify
that a generated wrapper's actual map dispatch pointer is the reflexive
IMap/IMapView<String or Guid, Object> pointer, rejecting another map wrapper
on the same COM identity while preserving .raw. Constrain the public view key
type to str or UUID.

Add a synthetic dual-map COM object, getter-error identity checks, strict
mypy/Pyright key controls, and generated Guid-map E2E coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Validate Object map dispatch metadata

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Regress released Object map views with updated runtime

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix observable Object map view dispatch

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Keep unpublished Object conversion notes out of preview.22

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant