7
7
from ... import schema as oai
8
8
from ... import utils
9
9
from ..errors import ParseError , PropertyError
10
+ from .enum_property import EnumProperty
10
11
from .property import Property
11
12
from .schemas import Class , Schemas , parse_reference_path
12
13
@@ -49,16 +50,57 @@ def get_imports(self, *, prefix: str) -> Set[str]:
49
50
return imports
50
51
51
52
53
+ def _values_are_subset (first : EnumProperty , second : EnumProperty ) -> bool :
54
+ return set (first .values .items ()) <= set (second .values .items ())
55
+
56
+
57
+ def _types_are_subset (first : EnumProperty , second : Property ) -> bool :
58
+ from . import IntProperty , StringProperty
59
+
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
81
+
82
+
52
83
def _merge_properties (first : Property , second : Property ) -> Union [Property , PropertyError ]:
53
- if first .__class__ != second .__class__ :
54
- return PropertyError (header = "Cannot merge properties" , detail = "Properties are two different types" )
55
84
nullable = first .nullable and second .nullable
56
85
required = first .required or second .required
57
- first = attr .evolve (first , nullable = nullable , required = required )
58
- second = attr .evolve (second , nullable = nullable , required = required )
59
- if first != second :
60
- return PropertyError (header = "Cannot merge properties" , detail = "Properties has conflicting values" )
61
- return first
86
+
87
+ err = None
88
+
89
+ if first .__class__ == second .__class__ :
90
+ first = attr .evolve (first , nullable = nullable , required = required )
91
+ second = attr .evolve (second , nullable = nullable , required = required )
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
+ )
62
104
63
105
64
106
class _PropertyData (NamedTuple ):
@@ -77,16 +119,18 @@ def _process_properties(
77
119
relative_imports : Set [str ] = set ()
78
120
required_set = set (data .required or [])
79
121
80
- def _check_existing ( prop : Property ) -> Union [ Property , PropertyError ]:
122
+ def _add_if_no_conflict ( new_prop : Property ) -> Optional [ PropertyError ]:
81
123
nonlocal properties
82
124
83
- existing = properties .get (prop .name )
84
- prop_or_error = _merge_properties (existing , prop ) if existing else prop
85
- if isinstance (prop_or_error , PropertyError ):
86
- prop_or_error .header = f"Found conflicting properties named { prop .name } when creating { class_name } "
87
- return prop_or_error
88
- properties [prop_or_error .name ] = prop_or_error
89
- 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
90
134
91
135
unprocessed_props = data .properties or {}
92
136
for sub_prop in data .allOf or []:
@@ -100,25 +144,24 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
100
144
if not isinstance (sub_model , ModelProperty ):
101
145
return PropertyError ("Cannot take allOf a non-object" )
102
146
for prop in chain (sub_model .required_properties , sub_model .optional_properties ):
103
- prop_or_error = _check_existing (prop )
104
- if isinstance ( prop_or_error , PropertyError ) :
105
- return prop_or_error
147
+ err = _add_if_no_conflict (prop )
148
+ if err is not None :
149
+ return err
106
150
else :
107
151
unprocessed_props .update (sub_prop .properties or {})
108
152
required_set .update (sub_prop .required or [])
109
153
110
154
for key , value in unprocessed_props .items ():
111
155
prop_required = key in required_set
156
+ prop_or_error : Union [Property , PropertyError , None ]
112
157
prop_or_error , schemas = property_from_data (
113
158
name = key , required = prop_required , data = value , schemas = schemas , parent_name = class_name , config = config
114
159
)
115
160
if isinstance (prop_or_error , Property ):
116
- prop_or_error = _check_existing (prop_or_error )
161
+ prop_or_error = _add_if_no_conflict (prop_or_error )
117
162
if isinstance (prop_or_error , PropertyError ):
118
163
return prop_or_error
119
164
120
- properties [prop_or_error .name ] = prop_or_error
121
-
122
165
required_properties = []
123
166
optional_properties = []
124
167
for prop in properties .values ():
0 commit comments