2020- Preserve actual pythonnet runtime behavior in generated stubs
2121
2222Usage:
23- python dotnet_stubsv3 .py <dll_folder> [output_folder]
23+ python dotnet_stubs .py <dll_folder> [output_folder]
2424
2525Example:
26- python dotnet_stubsv3 .py "C:\Program Files\Varian\RTM\18 .0\esapi\API" stubs_v3
26+ python dotnet_stubs .py "C:\Program Files\Varian\RTM\18 .0\esapi\API" stubs
2727"""
2828
2929import sys
@@ -99,6 +99,8 @@ class MethodInfo:
9999 is_constructor : bool = False
100100 is_generic : bool = False
101101 generic_parameters : List [str ] = field (default_factory = list )
102+ # Store original .NET parameter types for XML documentation lookup
103+ original_parameter_types : List [str ] = field (default_factory = list )
102104
103105
104106@dataclass
@@ -654,19 +656,38 @@ def _extract_assembly_types(self, assembly) -> Dict[str, TypeInfo]:
654656 try :
655657 # Get all types from assembly
656658 assembly_types = assembly .GetTypes ()
659+ logger .info (f"Found { len (assembly_types )} total types in assembly { assembly .GetName ().Name } " )
660+
661+ public_count = 0
662+ skipped_count = 0
663+ extracted_count = 0
657664
658665 for net_type in assembly_types :
659666 try :
667+ # Check if type is public
668+ if hasattr (net_type , 'IsPublic' ) and not net_type .IsPublic :
669+ continue
670+
671+ public_count += 1
672+
660673 # Skip compiler-generated and private types
661674 if self ._should_skip_type (net_type ):
675+ skipped_count += 1
676+ logger .debug (f"Skipping type: { net_type .FullName } " )
662677 continue
663678
664679 type_info = self ._extract_type_info (net_type )
665680 if type_info :
666681 types [type_info .full_name ] = type_info
682+ extracted_count += 1
683+ logger .debug (f"Extracted type: { type_info .full_name } " )
684+ else :
685+ logger .debug (f"Failed to extract type info for: { net_type .FullName } " )
667686
668687 except Exception as e :
669688 logger .debug (f"Failed to extract type info for { net_type } : { e } " )
689+
690+ logger .info (f"Assembly { assembly .GetName ().Name } : { public_count } public types, { skipped_count } skipped, { extracted_count } extracted" )
670691
671692 except Exception as e :
672693 logger .debug (f"Failed to get types from assembly { assembly } : { e } " )
@@ -747,6 +768,8 @@ def _extract_type_info(self, net_type) -> Optional[TypeInfo]:
747768 simple_name = net_type .Name
748769 namespace = net_type .Namespace or ""
749770
771+ logger .debug (f"Processing type: { full_name } " )
772+
750773 # Handle nested types
751774 if '+' in full_name :
752775 full_name = full_name .replace ('+' , '.' )
@@ -761,6 +784,8 @@ def _extract_type_info(self, net_type) -> Optional[TypeInfo]:
761784 is_enum = hasattr (net_type , 'IsEnum' ) and net_type .IsEnum
762785 is_struct = hasattr (net_type , 'IsValueType' ) and net_type .IsValueType and not is_enum
763786
787+ logger .debug (f"Type { full_name } : class={ is_class } , interface={ is_interface } , enum={ is_enum } , struct={ is_struct } " )
788+
764789 # Get base type
765790 base_type = None
766791 if hasattr (net_type , 'BaseType' ) and net_type .BaseType :
@@ -799,10 +824,13 @@ def _extract_type_info(self, net_type) -> Optional[TypeInfo]:
799824 self ._extract_fields (net_type , type_info )
800825 self ._extract_constructors (net_type , type_info )
801826
827+ logger .debug (f"Successfully extracted type info for { full_name } " )
802828 return type_info
803829
804830 except Exception as e :
805- logger .debug (f"Failed to extract type info for { net_type } : { e } " )
831+ logger .warning (f"Failed to extract type info for { net_type } : { e } " )
832+ import traceback
833+ logger .debug (traceback .format_exc ())
806834 return None
807835
808836 def _extract_properties (self , net_type , type_info : TypeInfo ):
@@ -862,14 +890,15 @@ def _should_include_method(self, method, type_info: TypeInfo, docs: Dict[str, Do
862890
863891 def _extract_methods (self , net_type , type_info : TypeInfo ):
864892 """Extract method information"""
865- logger .debug (f"Considering method: { method .Name } , IsSpecialName: { method .IsSpecialName } , IsPublic: { method .IsPublic } , IsStatic: { method .IsStatic } " )
866893 try :
867894 # Get all methods (public only, including inherited methods for complete API surface)
868895 binding_flags = BindingFlags .Public | BindingFlags .Instance | BindingFlags .Static
869896 methods = net_type .GetMethods (binding_flags )
870897
871898 for method in methods :
872899 try :
900+ logger .debug (f"Considering method: { method .Name } , IsSpecialName: { method .IsSpecialName } , IsPublic: { method .IsPublic } , IsStatic: { method .IsStatic } " )
901+
873902 # Use the filtering method to determine if this method should be included
874903 if not self ._should_include_method (method , type_info , self .docs ):
875904 continue
@@ -886,6 +915,10 @@ def _extract_methods(self, net_type, type_info: TypeInfo):
886915 param_name = param .Name or f"param{ param .Position } "
887916 param_type = NetTypeToPythonConverter .convert_type (param .ParameterType )
888917
918+ # Store original .NET type name for XML documentation lookup
919+ original_type_name = param .ParameterType .FullName or str (param .ParameterType )
920+ method_info .original_parameter_types .append (original_type_name )
921+
889922 param_info = ParameterInfo (
890923 name = self ._sanitize_identifier (param_name ),
891924 type_str = param_type ,
@@ -1701,7 +1734,7 @@ def _generate_class_stub(self, type_info: TypeInfo) -> List[str]:
17011734
17021735 # Generate fields as class variables
17031736 for field_name , field_info in sorted (type_info .fields .items ()):
1704- lines .extend (self ._generate_field_stub (field_info ))
1737+ lines .extend (self ._generate_field_stub (type_info , field_info ))
17051738
17061739 # Ensure class has at least one member
17071740 if len (lines ) <= 3 : # Just class def, docstring, and empty line
@@ -1730,6 +1763,15 @@ def _generate_enum_stub(self, type_info: TypeInfo) -> List[str]:
17301763 for field_name , field_info in sorted (type_info .fields .items ()):
17311764 if field_info .is_static :
17321765 sanitized_field_name = self ._sanitize_identifier (field_name )
1766+
1767+ # Look up field documentation for enum values
1768+ field_doc_key = f"{ type_info .full_name } .{ field_info .name } "
1769+ doc_info = self .docs .get (field_doc_key )
1770+
1771+ # Add documentation comment if available
1772+ if doc_info and doc_info .summary :
1773+ lines .append (f" # { doc_info .summary } " )
1774+
17331775 lines .append (f" { sanitized_field_name } : { type_info .simple_name } " )
17341776
17351777 if not type_info .fields :
@@ -1801,7 +1843,13 @@ def _generate_property_stub(self, type_info: TypeInfo, prop_info: PropertyInfo)
18011843 lines .append (f" def { prop_name } (cls, value: { prop_info .type_str } ) -> None:" )
18021844 else :
18031845 lines .append (f" def { prop_name } (self, value: { prop_info .type_str } ) -> None:" )
1804- lines .append (" \" \" \" Set property value.\" \" \" " )
1846+
1847+ # Use the same documentation for setter as getter, but modify it slightly
1848+ if doc_info and doc_info .summary :
1849+ setter_docstring = f'"""Set { prop_info .type_str } : { doc_info .summary } """'
1850+ else :
1851+ setter_docstring = f'"""Set { prop_info .type_str } : Property setter."""'
1852+ lines .append (f" { setter_docstring } " )
18051853 lines .append (" ..." )
18061854
18071855 return lines
@@ -1849,21 +1897,43 @@ def _generate_method_stub(self, type_info: TypeInfo, method_info: MethodInfo, is
18491897 params_str = ", " .join (params )
18501898 lines .append (f" def { method_name } ({ params_str } ) -> { method_info .return_type_str } :" )
18511899
1852- # Method docstring
1853- method_doc_key = f"{ type_info .full_name } .{ method_info .name } "
1900+ # Method docstring - build documentation key with parameter types for overload support
1901+ if method_info .original_parameter_types :
1902+ # Build full method signature for XML documentation lookup (like XML format)
1903+ param_types_str = "," .join (method_info .original_parameter_types )
1904+ method_doc_key = f"{ type_info .full_name } .{ method_info .name } ({ param_types_str } )"
1905+ else :
1906+ # Fallback to simple key for parameterless methods
1907+ method_doc_key = f"{ type_info .full_name } .{ method_info .name } "
1908+
18541909 doc_info = self .docs .get (method_doc_key )
1910+
1911+ # If no exact match found, try simple key as fallback
1912+ if not doc_info :
1913+ simple_key = f"{ type_info .full_name } .{ method_info .name } "
1914+ doc_info = self .docs .get (simple_key )
1915+
18551916 docstring = DocstringGenerator .generate_method_docstring (method_info , doc_info )
18561917 lines .append (f" { docstring } " )
18571918 lines .append (" ..." )
18581919
18591920 return lines
18601921
1861- def _generate_field_stub (self , field_info : FieldInfo ) -> List [str ]:
1862- """Generate field stub as class variable"""
1922+ def _generate_field_stub (self , type_info : TypeInfo , field_info : FieldInfo ) -> List [str ]:
1923+ """Generate field stub as class variable with documentation """
18631924 lines = []
18641925
18651926 field_name = self ._sanitize_identifier (field_info .name )
18661927
1928+ # Look up field documentation
1929+ field_doc_key = f"{ type_info .full_name } .{ field_info .name } "
1930+ doc_info = self .docs .get (field_doc_key )
1931+
1932+ # Add documentation comment if available
1933+ if doc_info and doc_info .summary :
1934+ # Add docstring comment above the field annotation
1935+ lines .append (f" # { doc_info .summary } " )
1936+
18671937 if field_info .is_static :
18681938 lines .append (f" { field_name } : { field_info .type_str } " )
18691939 else :
0 commit comments