Skip to content

Commit fdfbd13

Browse files
util.ImmutableValidatedObject: Support Optional[ImmutableValidatedObject]
1 parent 35ac020 commit fdfbd13

File tree

2 files changed

+48
-24
lines changed

2 files changed

+48
-24
lines changed

nixops/util.py

+33-23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import typeguard
1919
import inspect
2020
import shlex
21+
import typing
2122
from typing import (
2223
Callable,
2324
List,
@@ -33,6 +34,8 @@
3334
TypeVar,
3435
Generic,
3536
Iterable,
37+
Sequence,
38+
Type,
3639
)
3740

3841
import nixops.util
@@ -132,7 +135,7 @@ def __init__(self, *args: ImmutableValidatedObject, **kwargs):
132135
kw = {}
133136
for arg in args:
134137
if not isinstance(arg, ImmutableValidatedObject):
135-
raise TypeError("Arg not a Immutablevalidatedobject instance")
138+
raise TypeError("Arg not a ImmutableValidatedObject instance")
136139
kw.update(dict(arg))
137140
kw.update(kwargs)
138141

@@ -143,30 +146,37 @@ def __init__(self, *args: ImmutableValidatedObject, **kwargs):
143146
continue
144147
anno.update(x.__annotations__)
145148

146-
def _transform_value(key: Any, value: Any) -> Any:
147-
ann = anno.get(key)
148-
149+
def _transform_value(value: Any, value_type: Optional[Type]) -> Any:
149150
# Untyped, pass through
150-
if not ann:
151+
if not value_type:
151152
return value
152153

153-
if inspect.isclass(ann) and issubclass(ann, ImmutableValidatedObject):
154-
value = ann(**value)
155-
156-
# Support Sequence[ImmutableValidatedObject]
157-
if isinstance(value, tuple) and not isinstance(ann, str):
158-
new_value = []
159-
for v in value:
160-
for subann in ann.__args__:
161-
if inspect.isclass(subann) and issubclass(
162-
subann, ImmutableValidatedObject
163-
):
164-
new_value.append(subann(**v))
165-
else:
166-
new_value.append(v)
167-
value = tuple(new_value)
168-
169-
typeguard.check_type(key, value, ann)
154+
# Support ImmutableValidatedObject
155+
if (
156+
isinstance(value, Mapping)
157+
and inspect.isclass(value_type)
158+
and issubclass(value_type, ImmutableValidatedObject)
159+
):
160+
value = value_type(**value)
161+
162+
type_origin = typing.get_origin(value_type) # type: ignore[attr-defined]
163+
type_args = tuple(set(typing.get_args(value_type)) - {type(None)}) # type: ignore[attr-defined]
164+
if (
165+
type_origin is not None
166+
and len(type_args) == 1
167+
and inspect.isclass(type_args[0])
168+
and issubclass(type_args[0], ImmutableValidatedObject)
169+
):
170+
# Support Sequence[ImmutableValidatedObject]
171+
if isinstance(value, Sequence) and issubclass(tuple, type_origin):
172+
value = tuple(_transform_value(v, type_args[0]) for v in value)
173+
174+
# Support Optional[ImmutableValidatedObject]
175+
if type_origin is Union:
176+
if value is not None:
177+
value = _transform_value(value, type_args[0])
178+
179+
typeguard.check_type(key, value, value_type)
170180

171181
return value
172182

@@ -181,7 +191,7 @@ def _transform_value(key: Any, value: Any) -> Any:
181191
# is set this attribute is set on self before __init__ is called
182192
default = getattr(self, key) if hasattr(self, key) else None
183193
value = kw.get(key, default)
184-
setattr(self, key, _transform_value(key, value))
194+
setattr(self, key, _transform_value(value, anno.get(key)))
185195

186196
self._frozen = True
187197

tests/unit/test_util.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Sequence
1+
from typing import Sequence, Optional
22
import json
33
from nixops.logger import Logger
44
from io import StringIO
@@ -115,3 +115,17 @@ class WithSequence(util.ImmutableValidatedObject):
115115
subs: Sequence[SubResource]
116116

117117
WithSequence(subs=[SubResource(x=1), SubResource(x=2)])
118+
119+
# Test Optional[ImmutableValidatedObject]
120+
class WithOptional(util.ImmutableValidatedObject):
121+
sub: Optional[SubResource]
122+
sub_none: Optional[SubResource]
123+
subs: Optional[Sequence[SubResource]]
124+
subs_none: Optional[Sequence[SubResource]]
125+
126+
WithOptional(
127+
sub=SubResource(x=0),
128+
sub_none=None,
129+
subs=[SubResource(x=1), SubResource(x=2)],
130+
subs_none=None,
131+
)

0 commit comments

Comments
 (0)