11from 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
33
44import attr
55
@@ -50,56 +50,57 @@ def get_imports(self, *, prefix: str) -> Set[str]:
5050 return imports
5151
5252
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 :
6254 return set (first .values .items ()) <= set (second .values .items ())
6355
6456
65- def _is_subtype (first : Property , second : Property ) -> bool :
57+ def _types_are_subset (first : EnumProperty , second : Property ) -> bool :
6658 from . import IntProperty , StringProperty
6759
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
8081
8182
8283def _merge_properties (first : Property , second : Property ) -> Union [Property , PropertyError ]:
8384 nullable = first .nullable and second .nullable
8485 required = first .required or second .required
8586
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__ :
9390 first = attr .evolve (first , nullable = nullable , required = required )
9491 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+ )
103104
104105
105106class _PropertyData (NamedTuple ):
@@ -118,16 +119,18 @@ def _process_properties(
118119 relative_imports : Set [str ] = set ()
119120 required_set = set (data .required or [])
120121
121- def _check_existing ( prop : Property ) -> Union [ Property , PropertyError ]:
122+ def _add_if_no_conflict ( new_prop : Property ) -> Optional [ PropertyError ]:
122123 nonlocal properties
123124
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
131134
132135 unprocessed_props = data .properties or {}
133136 for sub_prop in data .allOf or []:
@@ -141,25 +144,24 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
141144 if not isinstance (sub_model , ModelProperty ):
142145 return PropertyError ("Cannot take allOf a non-object" )
143146 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
147150 else :
148151 unprocessed_props .update (sub_prop .properties or {})
149152 required_set .update (sub_prop .required or [])
150153
151154 for key , value in unprocessed_props .items ():
152155 prop_required = key in required_set
156+ prop_or_error : Union [Property , PropertyError , None ]
153157 prop_or_error , schemas = property_from_data (
154158 name = key , required = prop_required , data = value , schemas = schemas , parent_name = class_name , config = config
155159 )
156160 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 )
158162 if isinstance (prop_or_error , PropertyError ):
159163 return prop_or_error
160164
161- properties [prop_or_error .name ] = prop_or_error
162-
163165 required_properties = []
164166 optional_properties = []
165167 for prop in properties .values ():
0 commit comments