pybind11-stubgen icon indicating copy to clipboard operation
pybind11-stubgen copied to clipboard

Sorting is causing parent-child relationships to be defined out of order

Open daltairwalter opened this issue 1 year ago • 1 comments

When I run pybind11-stubgen, I see ouput:

class SimpleSetupArrayOutput(SimpleSetupEntity):  
    def getDataType(self) -> DATA_TYPE:  
        ...  
class SimpleSetupEntity:  
    def getName(self) -> str:  
        ... 

This is out of order because the SimpleSetupArrayOutput is a subclass of SimpleSetupEntity and this causes linting errors.  Is there a way to turn off sorting?  Or is it possible to sort and then reorder as necessary?

daltairwalter avatar Sep 16 '24 21:09 daltairwalter

I have this problem as well. The generated classes look like:

class BusyError(Error):
    pass
class Error(Exception):
    pass

sillydan1 avatar Sep 29 '24 08:09 sillydan1

I have the same issue with mixing classes that I define in the right order - but stubgen outputs them alphabetically, which breaks then exactly as the two examples above: https://github.com/ECP-WarpX/impactx/blob/2c97164cc687cacc6ba058482a93c1e1f0bf7ca9/src/python/elements.cpp#L143-L277

ax3l avatar Jan 07 '25 02:01 ax3l

@sizmailov Can we disable sorting somehow? I am running tools like ruff on my generated files anyway :)

ax3l avatar Jan 07 '25 02:01 ax3l

As a quick hack, I tried to remove the sorted() for classes in printer.py but it looks like the class sorting stays the same right now. It looks like the classes are stored in a List, thus I do not yet understand where the sorting comes in.

ax3l avatar Jan 07 '25 18:01 ax3l

I modified the inheritance test in #238 as a demo but am not sure how to fix the issue yet :)

ax3l avatar Jan 09 '25 22:01 ax3l

Ok, if I append reverse=True to this line, then it changes the sorting: https://github.com/sizmailov/pybind11-stubgen/blob/cf58aa6c7f0655c2f830b6964aa48baff868b891/pybind11_stubgen/printer.py#L228

If I just remove the sorted() altogether then it is still sorting the classes alphabetically, so there must be an earlier sort of some kind happening for module.classes.

ax3l avatar Jan 30 '25 21:01 ax3l

I think the issue might be that parse.py uses inspect.getmembers to get the members of a module and that one is sorted alphabetically. After some Google & LLM queries, we could use the ast module instead to query and preserve order.

ax3l avatar Jan 30 '25 21:01 ax3l

@ax3l cited these lines in printer.py:

https://github.com/sizmailov/pybind11-stubgen/blob/cf58aa6c7f0655c2f830b6964aa48baff868b891/pybind11_stubgen/printer.py#L228

To fix this (at least for my case), I performed a topological sort of the classes. I sort the class names because the Class type is not hashable.

import graphlib
import itertools

sorted_names = list(
    graphlib.TopologicalSorter(
        {c.name: set(itertools.chain.from_iterable(c.bases)) for c in module.classes}
    ).static_order()
)
# Use `filter` to drop `None` values returned when a base is outside the collection.
sorted_classes = filter(lambda c: c, map({c.name: c for c in module.classes}.get, sorted_names))

for class_ in sorted_classes:
    result.extend(self.print_class(class_))

(Inheritance relationships should form a DAG, so I think this topological sort should generally succeed.)

nmusolino avatar Sep 05 '25 18:09 nmusolino