@@ -1062,141 +1062,11 @@ def annotations_to_string(annotations):
10621062 }
10631063
10641064
1065- def _get_annotations_for_partialmethod (partialmethod_obj , format ):
1066- """Get annotations for a functools.partialmethod object.
1067-
1068- Returns annotations for the wrapped function, but only for parameters
1069- that haven't been bound by the partial application. The first parameter
1070- (usually 'self' or 'cls') is kept since partialmethod is unbound.
1071- """
1072- import inspect
1073-
1074- # Get the wrapped function
1075- func = partialmethod_obj .func
1076-
1077- # Get annotations from the wrapped function
1078- func_annotations = get_annotations (func , format = format )
1079-
1080- if not func_annotations :
1081- return {}
1082-
1083- # For partialmethod, we need to simulate the signature calculation
1084- # The first parameter (self/cls) should remain, but bound args should be removed
1085- try :
1086- # Get the function signature
1087- func_sig = inspect .signature (func )
1088- func_params = list (func_sig .parameters .keys ())
1089-
1090- if not func_params :
1091- return func_annotations
1092-
1093- # Calculate which parameters are bound by the partialmethod
1094- partial_args = partialmethod_obj .args or ()
1095- partial_keywords = partialmethod_obj .keywords or {}
1096-
1097- # Build new annotations dict in proper order
1098- # (parameters first, then return)
1099- new_annotations = {}
1100-
1101- # The first parameter (self/cls) is always kept for unbound partialmethod
1102- first_param = func_params [0 ]
1103- if first_param in func_annotations :
1104- new_annotations [first_param ] = func_annotations [first_param ]
1105-
1106- # For partialmethod, positional args bind to parameters AFTER the first one
1107- # So if func is (self, a, b, c) and partialmethod.args=(1,)
1108- # Then 'self' stays, 'a' is bound, 'b' and 'c' remain
1109-
1110- remaining_params = func_params [1 :]
1111- num_positional_bound = len (partial_args )
1112-
1113- for i , param_name in enumerate (remaining_params ):
1114- # Skip if this param is bound positionally
1115- if i < num_positional_bound :
1116- continue
1117-
1118- # For keyword binding: keep the annotation (keyword sets default, doesn't remove param)
1119- if param_name in partial_keywords :
1120- if param_name in func_annotations :
1121- new_annotations [param_name ] = func_annotations [param_name ]
1122- continue
1123-
1124- # This parameter is not bound, keep its annotation
1125- if param_name in func_annotations :
1126- new_annotations [param_name ] = func_annotations [param_name ]
1127-
1128- # Add return annotation at the end
1129- if 'return' in func_annotations :
1130- new_annotations ['return' ] = func_annotations ['return' ]
1131-
1132- return new_annotations
1133-
1134- except (ValueError , TypeError ):
1135- # If we can't process, return the original annotations
1136- return func_annotations
1137-
1138-
1139- def _get_annotations_for_partial (partial_obj , format ):
1140- """Get annotations for a functools.partial object.
1141-
1142- Returns annotations for the wrapped function, but only for parameters
1143- that haven't been bound by the partial application.
1144- """
1145- import inspect
1146-
1147- # Get the wrapped function
1148- func = partial_obj .func
1149-
1150- # Get annotations from the wrapped function
1151- func_annotations = get_annotations (func , format = format )
1152-
1153- if not func_annotations :
1154- return {}
1155-
1156- # Get the signature to determine which parameters are bound
1157- try :
1158- sig = inspect .signature (partial_obj )
1159- except (ValueError , TypeError ):
1160- # If we can't get signature, return empty dict
1161- return {}
1162-
1163- # Build new annotations dict with only unbound parameters
1164- # (parameters first, then return)
1165- new_annotations = {}
1166-
1167- # Only include annotations for parameters that still exist in partial's signature
1168- for param_name in sig .parameters :
1169- if param_name in func_annotations :
1170- new_annotations [param_name ] = func_annotations [param_name ]
1171-
1172- # Add return annotation at the end
1173- if 'return' in func_annotations :
1174- new_annotations ['return' ] = func_annotations ['return' ]
1175-
1176- return new_annotations
1177-
1178-
11791065def _get_and_call_annotate (obj , format ):
11801066 """Get the __annotate__ function and call it.
11811067
11821068 May not return a fresh dictionary.
11831069 """
1184- import functools
1185-
1186- # Handle functools.partialmethod objects (unbound)
1187- # Check for __partialmethod__ attribute first
1188- try :
1189- partialmethod = obj .__partialmethod__
1190- except AttributeError :
1191- pass
1192- else :
1193- if isinstance (partialmethod , functools .partialmethod ):
1194- return _get_annotations_for_partialmethod (partialmethod , format )
1195-
1196- # Handle functools.partial objects
1197- if isinstance (obj , functools .partial ):
1198- return _get_annotations_for_partial (obj , format )
1199-
12001070 annotate = getattr (obj , "__annotate__" , None )
12011071 if annotate is not None :
12021072 ann = call_annotate_function (annotate , format , owner = obj )
@@ -1214,21 +1084,6 @@ def _get_dunder_annotations(obj):
12141084
12151085 Does not return a fresh dictionary.
12161086 """
1217- # Check for functools.partialmethod - skip __annotations__ and use __annotate__ path
1218- import functools
1219- try :
1220- partialmethod = obj .__partialmethod__
1221- if isinstance (partialmethod , functools .partialmethod ):
1222- # Return None to trigger _get_and_call_annotate
1223- return None
1224- except AttributeError :
1225- pass
1226-
1227- # Check for functools.partial - skip __annotations__ and use __annotate__ path
1228- if isinstance (obj , functools .partial ):
1229- # Return None to trigger _get_and_call_annotate
1230- return None
1231-
12321087 # This special case is needed to support types defined under
12331088 # from __future__ import annotations, where accessing the __annotations__
12341089 # attribute directly might return annotations for the wrong class.
0 commit comments