Skip to content

Commit 9898e14

Browse files
Document union property key workaround
This adds to the doc strings for property keys and typed properties a warning about using union types and a description of the workaround. It's essentially the same thing in four places, but it's a big enough deal to warrant being pasted all over. I also added some test cases to make sure it works.
1 parent 072adf4 commit 9898e14

File tree

3 files changed

+111
-6
lines changed

3 files changed

+111
-6
lines changed

packages/smithy-core/src/smithy_core/interfaces/__init__.py

+41-3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ class PropertyKey[T](Protocol):
118118
Used with :py:class:`Context` to set and get typed values.
119119
120120
For a concrete implementation, see :py:class:`smithy_core.types.PropertyKey`.
121+
122+
Note that unions and other special types cannot easily be used here due to being
123+
incompatible with ``type[T]``. PEP747 proposes a fix to this case, but it has not
124+
yet been accepted. In the meantime, there is a workaround. The PropertyKey must
125+
be assigned to an explicitly typed variable, and the ``value_type`` parameter of
126+
the constructor must have a ``# type: ignore`` comment, like so:
127+
128+
.. code-block:: python
129+
130+
UNION_PROPERTY: PropertyKey[str | int] = PropertyKey(
131+
key="union",
132+
value_type=str | int # type: ignore
133+
)
134+
135+
Type checkers will be able to use such a property as expected, and the
136+
``value_type`` property may still be used in ``isinstance`` checks since it also
137+
supports union types as of Python 3.10.
121138
"""
122139

123140
key: str
@@ -151,11 +168,32 @@ class TypedProperties(Protocol):
151168
properties = TypedProperties()
152169
properties[foo] = "bar"
153170
154-
assert assert_type(properties[foo], str) == "bar
155-
assert assert_type(properties["foo"], Any) == "bar
156-
171+
assert assert_type(properties[foo], str) == "bar"
172+
assert assert_type(properties["foo"], Any) == "bar"
157173
158174
For a concrete implementation, see :py:class:`smithy_core.types.TypedProperties`.
175+
176+
Note that unions and other special types cannot easily be used here due to being
177+
incompatible with ``type[T]``. PEP747 proposes a fix to this case, but it has not
178+
yet been accepted. In the meantime, there is a workaround. The PropertyKey must
179+
be assigned to an explicitly typed variable, and the ``value_type`` parameter of
180+
the constructor must have a ``# type: ignore`` comment, like so:
181+
182+
.. code-block:: python
183+
184+
UNION_PROPERTY: PropertyKey[str | int] = PropertyKey(
185+
key="union",
186+
value_type=str | int # type: ignore
187+
)
188+
189+
properties = TypedProperties()
190+
properties[UNION_PROPERTY] = "foo"
191+
192+
assert assert_type(properties[UNION_PROPERTY], str | int) == "foo"
193+
194+
Type checkers will be able to use such a property as expected, and the
195+
``value_type`` property may still be used in ``isinstance`` checks since it also
196+
supports union types as of Python 3.10.
159197
"""
160198

161199
@overload

packages/smithy-core/src/smithy_core/types.py

+43-3
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,25 @@ def format(self, *args: object, **kwargs: str) -> str:
161161

162162
@dataclass(kw_only=True, frozen=True, slots=True, init=False)
163163
class PropertyKey[T](_PropertyKey[T]):
164-
"""A typed property key."""
164+
"""A typed property key.
165+
166+
Note that unions and other special types cannot easily be used here due to being
167+
incompatible with ``type[T]``. PEP747 proposes a fix to this case, but it has not
168+
yet been accepted. In the meantime, there is a workaround. The PropertyKey must
169+
be assigned to an explicitly typed variable, and the ``value_type`` parameter of
170+
the constructor must have a ``# type: ignore`` comment, like so:
171+
172+
.. code-block:: python
173+
174+
UNION_PROPERTY: PropertyKey[str | int] = PropertyKey(
175+
key="union",
176+
value_type=str | int # type: ignore
177+
)
178+
179+
Type checkers will be able to use such a property as expected, and the
180+
``value_type`` property may still be used in ``isinstance`` checks since it also
181+
supports union types as of Python 3.10.
182+
"""
165183

166184
key: str
167185
"""The string key used to access the value."""
@@ -192,8 +210,30 @@ class TypedProperties(UserDict[str, Any], _TypedProperties):
192210
properties = TypedProperties()
193211
properties[foo] = "bar"
194212
195-
assert assert_type(properties[foo], str) == "bar
196-
assert assert_type(properties["foo"], Any) == "bar
213+
assert assert_type(properties[foo], str) == "bar"
214+
assert assert_type(properties["foo"], Any) == "bar"
215+
216+
Note that unions and other special types cannot easily be used here due to being
217+
incompatible with ``type[T]``. PEP747 proposes a fix to this case, but it has not
218+
yet been accepted. In the meantime, there is a workaround. The PropertyKey must
219+
be assigned to an explicitly typed variable, and the ``value_type`` parameter of
220+
the constructor must have a ``# type: ignore`` comment, like so:
221+
222+
.. code-block:: python
223+
224+
UNION_PROPERTY: PropertyKey[str | int] = PropertyKey(
225+
key="union",
226+
value_type=str | int # type: ignore
227+
)
228+
229+
properties = TypedProperties()
230+
properties[UNION_PROPERTY] = "foo"
231+
232+
assert assert_type(properties[UNION_PROPERTY], str | int) == "foo"
233+
234+
Type checkers will be able to use such a property as expected, and the
235+
``value_type`` property may still be used in ``isinstance`` checks since it also
236+
supports union types as of Python 3.10.
197237
"""
198238

199239
@overload

packages/smithy-core/tests/unit/test_types.py

+27
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,30 @@ def test_properties_typed_pop() -> None:
323323
assert "foo" not in properties.data
324324

325325
assert properties.pop(foo_key) is None
326+
327+
328+
def test_union_property() -> None:
329+
properties = TypedProperties()
330+
union: PropertyKey[str | int] = PropertyKey(
331+
key="union",
332+
value_type=str | int, # type: ignore
333+
)
334+
335+
properties[union] = "foo"
336+
assert assert_type(properties[union], str | int) == "foo"
337+
assert assert_type(properties.get(union), str | int | None) == "foo"
338+
assert assert_type(properties.get(union, b"foo"), str | int | bytes) == "foo"
339+
assert assert_type(properties.pop(union), str | int | None) == "foo"
340+
properties[union] = "foo"
341+
assert assert_type(properties.pop(union, b"bar"), str | int | bytes) == "foo"
342+
343+
properties[union] = 1
344+
assert assert_type(properties[union], str | int) == 1
345+
assert assert_type(properties.get(union), str | int | None) == 1
346+
assert assert_type(properties.get(union, b"foo"), str | int | bytes) == 1
347+
assert assert_type(properties.pop(union), str | int | None) == 1
348+
properties[union] = 1
349+
assert assert_type(properties.pop(union, b"bar"), str | int | bytes) == 1
350+
351+
with pytest.raises(ValueError):
352+
properties[union] = b"bar" # type: ignore

0 commit comments

Comments
 (0)