1
1
from itertools import chain
2
- from typing import ClassVar , Dict , List , NamedTuple , Optional , Set , Tuple , Union , cast
2
+ from typing import ClassVar , Dict , List , NamedTuple , Optional , Set , Tuple , Union
3
3
4
4
import attr
5
5
@@ -50,56 +50,57 @@ def get_imports(self, *, prefix: str) -> Set[str]:
50
50
return imports
51
51
52
52
53
- def _is_string_enum (prop : Property ) -> bool :
54
- return isinstance (prop , EnumProperty ) and prop .value_type == str
55
-
56
-
57
- def _is_int_enum (prop : Property ) -> bool :
58
- return isinstance (prop , EnumProperty ) and prop .value_type == int
59
-
60
-
61
- def values_are_subset (first : EnumProperty , second : EnumProperty ) -> bool :
53
+ def _values_are_subset (first : EnumProperty , second : EnumProperty ) -> bool :
62
54
return set (first .values .items ()) <= set (second .values .items ())
63
55
64
56
65
- def _is_subtype (first : Property , second : Property ) -> bool :
57
+ def _types_are_subset (first : EnumProperty , second : Property ) -> bool :
66
58
from . import IntProperty , StringProperty
67
59
68
- return any (
69
- [
70
- _is_string_enum (first ) and isinstance (second , StringProperty ),
71
- _is_int_enum (first ) and isinstance (second , IntProperty ),
72
- _is_string_enum (first ) and _is_string_enum (second )
73
- # cast because MyPy fails to deduce type
74
- and values_are_subset (cast (EnumProperty , first ), cast (EnumProperty , second )),
75
- _is_int_enum (first ) and _is_int_enum (second )
76
- # cast because MyPy fails to deduce type
77
- and values_are_subset (cast (EnumProperty , first ), cast (EnumProperty , second )),
78
- ]
79
- )
60
+ if first .value_type == int and isinstance (second , IntProperty ):
61
+ return True
62
+ if first .value_type == str and isinstance (second , StringProperty ):
63
+ return True
64
+ return False
65
+
66
+
67
+ def _enum_subset (first : Property , second : Property ) -> Optional [EnumProperty ]:
68
+ """Return the EnumProperty that is the subset of the other, if possible."""
69
+
70
+ if isinstance (first , EnumProperty ):
71
+ if isinstance (second , EnumProperty ):
72
+ if _values_are_subset (first , second ):
73
+ return first
74
+ if _values_are_subset (second , first ):
75
+ return second
76
+ return None
77
+ return first if _types_are_subset (first , second ) else None
78
+ if isinstance (second , EnumProperty ) and _types_are_subset (second , first ):
79
+ return second
80
+ return None
80
81
81
82
82
83
def _merge_properties (first : Property , second : Property ) -> Union [Property , PropertyError ]:
83
84
nullable = first .nullable and second .nullable
84
85
required = first .required or second .required
85
86
86
- if _is_subtype (first , second ):
87
- first = attr .evolve (first , nullable = nullable , required = required )
88
- return first
89
- elif _is_subtype (second , first ):
90
- second = attr .evolve (second , nullable = nullable , required = required )
91
- return second
92
- elif first .__class__ == second .__class__ :
87
+ err = None
88
+
89
+ if first .__class__ == second .__class__ :
93
90
first = attr .evolve (first , nullable = nullable , required = required )
94
91
second = attr .evolve (second , nullable = nullable , required = required )
95
- if first != second :
96
- return PropertyError (header = "Cannot merge properties" , detail = "Properties has conflicting values" )
97
- return first
98
- else :
99
- return PropertyError (
100
- header = "Cannot merge properties" ,
101
- detail = f"{ first .__class__ } , { second .__class__ } Properties have incompatible types" ,
102
- )
92
+ if first == second :
93
+ return first
94
+ err = PropertyError (header = "Cannot merge properties" , detail = "Properties has conflicting values" )
95
+
96
+ enum_subset = _enum_subset (first , second )
97
+ if enum_subset is not None :
98
+ return attr .evolve (enum_subset , nullable = nullable , required = required )
99
+
100
+ return err or PropertyError (
101
+ header = "Cannot merge properties" ,
102
+ detail = f"{ first .__class__ } , { second .__class__ } Properties have incompatible types" ,
103
+ )
103
104
104
105
105
106
class _PropertyData (NamedTuple ):
@@ -118,16 +119,18 @@ def _process_properties(
118
119
relative_imports : Set [str ] = set ()
119
120
required_set = set (data .required or [])
120
121
121
- def _check_existing ( prop : Property ) -> Union [ Property , PropertyError ]:
122
+ def _add_if_no_conflict ( new_prop : Property ) -> Optional [ PropertyError ]:
122
123
nonlocal properties
123
124
124
- existing = properties .get (prop .name )
125
- prop_or_error = _merge_properties (existing , prop ) if existing else prop
126
- if isinstance (prop_or_error , PropertyError ):
127
- prop_or_error .header = f"Found conflicting properties named { prop .name } when creating { class_name } "
128
- return prop_or_error
129
- properties [prop_or_error .name ] = prop_or_error
130
- return prop_or_error
125
+ existing = properties .get (new_prop .name )
126
+ merged_prop_or_error = _merge_properties (existing , new_prop ) if existing else new_prop
127
+ if isinstance (merged_prop_or_error , PropertyError ):
128
+ merged_prop_or_error .header = (
129
+ f"Found conflicting properties named { new_prop .name } when creating { class_name } "
130
+ )
131
+ return merged_prop_or_error
132
+ properties [merged_prop_or_error .name ] = merged_prop_or_error
133
+ return None
131
134
132
135
unprocessed_props = data .properties or {}
133
136
for sub_prop in data .allOf or []:
@@ -141,25 +144,24 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
141
144
if not isinstance (sub_model , ModelProperty ):
142
145
return PropertyError ("Cannot take allOf a non-object" )
143
146
for prop in chain (sub_model .required_properties , sub_model .optional_properties ):
144
- prop_or_error = _check_existing (prop )
145
- if isinstance ( prop_or_error , PropertyError ) :
146
- return prop_or_error
147
+ err = _add_if_no_conflict (prop )
148
+ if err is not None :
149
+ return err
147
150
else :
148
151
unprocessed_props .update (sub_prop .properties or {})
149
152
required_set .update (sub_prop .required or [])
150
153
151
154
for key , value in unprocessed_props .items ():
152
155
prop_required = key in required_set
156
+ prop_or_error : Union [Property , PropertyError , None ]
153
157
prop_or_error , schemas = property_from_data (
154
158
name = key , required = prop_required , data = value , schemas = schemas , parent_name = class_name , config = config
155
159
)
156
160
if isinstance (prop_or_error , Property ):
157
- prop_or_error = _check_existing (prop_or_error )
161
+ prop_or_error = _add_if_no_conflict (prop_or_error )
158
162
if isinstance (prop_or_error , PropertyError ):
159
163
return prop_or_error
160
164
161
- properties [prop_or_error .name ] = prop_or_error
162
-
163
165
required_properties = []
164
166
optional_properties = []
165
167
for prop in properties .values ():
0 commit comments