1
1
from collections .abc import Callable
2
- from typing import Any
2
+ from typing import Any , Dict , TypeVar , Union , cast
3
3
4
4
from attr import evolve
5
5
11
11
from openapi_python_client .parser .properties .protocol import PropertyProtocol
12
12
from openapi_python_client .parser .properties .string import StringProperty
13
13
14
+ # Note that in this file we have to use PropertyProtocol instead of the union type Property,
15
+ # to avoid circular imports.
16
+ PropertyT = TypeVar ("PropertyT" , bound = PropertyProtocol )
17
+
14
18
15
19
def merge_properties (prop1 : PropertyProtocol , prop2 : PropertyProtocol ) -> PropertyProtocol :
16
20
"""Attempt to create a new property that incorporates the behavior of both.
@@ -56,11 +60,11 @@ def _merge_same_type(prop1: PropertyProtocol, prop2: PropertyProtocol) -> Proper
56
60
return prop1
57
61
58
62
if isinstance (prop1 , StringProperty ):
59
- return _merge_string (prop1 , prop2 )
63
+ return _merge_string (prop1 , cast ( StringProperty , prop2 ) )
60
64
61
65
if isinstance (prop1 , ListProperty ):
62
66
# There's no clear way to represent the intersection of two different list types. Fail in this case.
63
- if prop1 .inner_property != prop2 .inner_property :
67
+ if prop1 .inner_property != cast ( ListProperty , prop2 ) .inner_property :
64
68
raise ValueError ("can't redefine a list property with a different element type" )
65
69
66
70
# For all other property types, there aren't any special attributes that affect validation, so just
@@ -71,12 +75,12 @@ def _merge_same_type(prop1: PropertyProtocol, prop2: PropertyProtocol) -> Proper
71
75
def _merge_string (prop1 : StringProperty , prop2 : StringProperty ) -> StringProperty :
72
76
# If max_length was specified for both, the smallest value takes precedence. If only one of them
73
77
# specifies it, _combine_values will pick that one.
74
- max_length : int | None = _combine_values (prop1 .max_length , prop2 .max_length , lambda a , b : min ([a , b ]))
78
+ max_length : Union [ int , None ] = _combine_values (prop1 .max_length , prop2 .max_length , lambda a , b : min ([a , b ]))
75
79
76
80
# If pattern was specified for both, we have a problem. OpenAPI has no logical objection to this;
77
81
# it would just mean the value must match both of the patterns to be valid. But we have no way to
78
82
# represent this in our internal model currently.
79
- pattern : str | None | ValueError = _combine_values (
83
+ pattern : Union [ str , None , ValueError ] = _combine_values (
80
84
prop1 .pattern , prop2 .pattern , lambda a , b : ValueError ("specified two different regex patterns" )
81
85
)
82
86
if isinstance (pattern , ValueError ):
@@ -89,7 +93,7 @@ def _merge_numeric(prop1: PropertyProtocol, prop2: PropertyProtocol) -> IntPrope
89
93
# Here, one of the properties was defined as "int" and the other was just a general number (which
90
94
# we call FloatProperty). "Must be integer" is the stricter validation rule, so the result must be
91
95
# an IntProperty.
92
- int_prop = prop1 if isinstance (prop1 , IntProperty ) else prop2
96
+ int_prop = prop1 if isinstance (prop1 , IntProperty ) else cast ( IntProperty , prop2 )
93
97
result = _merge_common_attributes (int_prop , prop1 , prop2 )
94
98
if result .default is not None :
95
99
if isinstance (result .default , float ) and not result .default .is_integer ():
@@ -108,7 +112,7 @@ def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumPr
108
112
if isinstance (prop1 , EnumProperty ) and isinstance (prop2 , EnumProperty ):
109
113
# We want the narrowest validation rules that fit both, so use whichever values list is a
110
114
# subset of the other.
111
- values : dict [str , ValueType ]
115
+ values : Dict [str , ValueType ]
112
116
if _values_are_subset (prop1 , prop2 ):
113
117
values = prop1 .values
114
118
elif _values_are_subset (prop2 , prop1 ):
@@ -118,7 +122,7 @@ def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumPr
118
122
return _merge_common_attributes (evolve (prop1 , values = values ), prop2 )
119
123
120
124
# If enum values were specified for just one of the properties, use those.
121
- enum_prop = prop1 if isinstance (prop1 , EnumProperty ) else prop2
125
+ enum_prop = prop1 if isinstance (prop1 , EnumProperty ) else cast ( EnumProperty , prop2 )
122
126
non_enum_prop = prop2 if isinstance (prop1 , EnumProperty ) else prop1
123
127
if (isinstance (non_enum_prop , IntProperty ) and enum_prop .value_type is int ) or (
124
128
isinstance (non_enum_prop , StringProperty ) and enum_prop .value_type is str
@@ -127,7 +131,7 @@ def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumPr
127
131
raise ValueError ("defined with two incompatible types" )
128
132
129
133
130
- def _merge_common_attributes (base : PropertyProtocol , * extend_with : PropertyProtocol ) -> PropertyProtocol :
134
+ def _merge_common_attributes (base : PropertyT , * extend_with : PropertyProtocol ) -> PropertyT :
131
135
"""Create a new instance based on base, overriding basic attributes with values from extend_with, in order.
132
136
133
137
For "default", "description", and "example", a non-None value overrides any value from a previously
@@ -140,7 +144,7 @@ def _merge_common_attributes(base: PropertyProtocol, *extend_with: PropertyProto
140
144
current = base
141
145
for override in extend_with :
142
146
current = evolve (
143
- current ,
147
+ current , # type: ignore # can't prove that every property type is an attrs class, but it is
144
148
required = current .required or override .required ,
145
149
default = override .default or current .default ,
146
150
description = override .description or current .description ,
@@ -153,7 +157,7 @@ def _values_are_subset(prop1: EnumProperty, prop2: EnumProperty) -> bool:
153
157
return set (prop1 .values .items ()) <= set (prop2 .values .items ())
154
158
155
159
156
- def _combine_values (value1 : Any , value2 : Any , combinator : Callable ) -> Any :
160
+ def _combine_values (value1 : Any , value2 : Any , combinator : Callable [[ Any , Any ], Any ] ) -> Any :
157
161
if value1 == value2 :
158
162
return value1
159
163
if value1 is None :
0 commit comments