xref: /petsc/src/binding/petsc4py/docs/source/apidoc.py (revision 0898713fbecf5e265dbd1d072d2bffc3dcf92948)
155a74a43SLisandro Dalcinimport os
255a74a43SLisandro Dalcinimport sys
355a74a43SLisandro Dalcinimport inspect
455a74a43SLisandro Dalcinimport textwrap
5e4efd8cbSStefano Zampinifrom sphinx.util import logging
6e4efd8cbSStefano Zampinilogger = logging.getLogger(__name__)
755a74a43SLisandro Dalcin
855a74a43SLisandro Dalcindef is_cyfunction(obj):
955a74a43SLisandro Dalcin    return type(obj).__name__ == 'cython_function_or_method'
1055a74a43SLisandro Dalcin
1155a74a43SLisandro Dalcin
1255a74a43SLisandro Dalcindef is_function(obj):
1355a74a43SLisandro Dalcin    return (
1455a74a43SLisandro Dalcin        inspect.isbuiltin(obj)
1555a74a43SLisandro Dalcin        or is_cyfunction(obj)
1655a74a43SLisandro Dalcin        or type(obj) is type(ord)
1755a74a43SLisandro Dalcin    )
1855a74a43SLisandro Dalcin
1955a74a43SLisandro Dalcin
2055a74a43SLisandro Dalcindef is_method(obj):
2155a74a43SLisandro Dalcin    return (
2255a74a43SLisandro Dalcin        inspect.ismethoddescriptor(obj)
2355a74a43SLisandro Dalcin        or inspect.ismethod(obj)
2455a74a43SLisandro Dalcin        or is_cyfunction(obj)
2555a74a43SLisandro Dalcin        or type(obj) in (
2655a74a43SLisandro Dalcin            type(str.index),
2755a74a43SLisandro Dalcin            type(str.__add__),
2855a74a43SLisandro Dalcin            type(str.__new__),
2955a74a43SLisandro Dalcin        )
3055a74a43SLisandro Dalcin    )
3155a74a43SLisandro Dalcin
3255a74a43SLisandro Dalcin
3355a74a43SLisandro Dalcindef is_classmethod(obj):
3455a74a43SLisandro Dalcin    return (
3555a74a43SLisandro Dalcin        inspect.isbuiltin(obj)
3655a74a43SLisandro Dalcin        or type(obj).__name__ in (
3755a74a43SLisandro Dalcin            'classmethod',
3855a74a43SLisandro Dalcin            'classmethod_descriptor',
3955a74a43SLisandro Dalcin        )
4055a74a43SLisandro Dalcin    )
4155a74a43SLisandro Dalcin
4255a74a43SLisandro Dalcin
4355a74a43SLisandro Dalcindef is_staticmethod(obj):
4455a74a43SLisandro Dalcin    return (
4555a74a43SLisandro Dalcin        type(obj).__name__ in (
4655a74a43SLisandro Dalcin            'staticmethod',
4755a74a43SLisandro Dalcin        )
4855a74a43SLisandro Dalcin    )
4955a74a43SLisandro Dalcin
5055a74a43SLisandro Dalcindef is_constant(obj):
5155a74a43SLisandro Dalcin    return isinstance(obj, (int, float, str, dict))
5255a74a43SLisandro Dalcin
5355a74a43SLisandro Dalcindef is_datadescr(obj):
5455a74a43SLisandro Dalcin    return inspect.isdatadescriptor(obj) and not hasattr(obj, 'fget')
5555a74a43SLisandro Dalcin
5655a74a43SLisandro Dalcin
5755a74a43SLisandro Dalcindef is_property(obj):
5855a74a43SLisandro Dalcin    return inspect.isdatadescriptor(obj) and hasattr(obj, 'fget')
5955a74a43SLisandro Dalcin
6055a74a43SLisandro Dalcin
6155a74a43SLisandro Dalcindef is_class(obj):
6255a74a43SLisandro Dalcin    return inspect.isclass(obj) or type(obj) is type(int)
6355a74a43SLisandro Dalcin
6455a74a43SLisandro Dalcin
6555a74a43SLisandro Dalcinclass Lines(list):
6655a74a43SLisandro Dalcin
6755a74a43SLisandro Dalcin    INDENT = " " * 4
6855a74a43SLisandro Dalcin    level = 0
6955a74a43SLisandro Dalcin
7055a74a43SLisandro Dalcin    @property
7155a74a43SLisandro Dalcin    def add(self):
7255a74a43SLisandro Dalcin        return self
7355a74a43SLisandro Dalcin
7455a74a43SLisandro Dalcin    @add.setter
7555a74a43SLisandro Dalcin    def add(self, lines):
7655a74a43SLisandro Dalcin        if lines is None:
7755a74a43SLisandro Dalcin            return
7855a74a43SLisandro Dalcin        if isinstance(lines, str):
7955a74a43SLisandro Dalcin            lines = textwrap.dedent(lines).strip().split('\n')
8055a74a43SLisandro Dalcin        indent = self.INDENT * self.level
8155a74a43SLisandro Dalcin        for line in lines:
8255a74a43SLisandro Dalcin            self.append(indent + line)
8355a74a43SLisandro Dalcin
8455a74a43SLisandro Dalcin
8555a74a43SLisandro Dalcindef signature(obj):
8655a74a43SLisandro Dalcin    doc = obj.__doc__
8755a74a43SLisandro Dalcin    doc = doc or f"{obj.__name__}: Any"  # FIXME remove line
8855a74a43SLisandro Dalcin    sig = doc.partition('\n')[0].split('.', 1)[-1]
8955a74a43SLisandro Dalcin    return sig or None
9055a74a43SLisandro Dalcin
9155a74a43SLisandro Dalcin
9255a74a43SLisandro Dalcindef docstring(obj):
9355a74a43SLisandro Dalcin    doc = obj.__doc__
9455a74a43SLisandro Dalcin    doc = doc or '' # FIXME
95b3f8c7a1SStefano Zampini    link = None
96e4efd8cbSStefano Zampini    sig = None
97e4efd8cbSStefano Zampini    cl = is_class(obj)
98e4efd8cbSStefano Zampini    if cl:
9955a74a43SLisandro Dalcin        doc = doc.strip()
10055a74a43SLisandro Dalcin    else:
101e4efd8cbSStefano Zampini        sig, _, doc = doc.partition('\n')
102b3f8c7a1SStefano Zampini        doc, _, link = doc.rpartition('\n')
103b3f8c7a1SStefano Zampini
10455a74a43SLisandro Dalcin    summary, _, docbody = doc.partition('\n')
10555a74a43SLisandro Dalcin    summary = summary.strip()
10655a74a43SLisandro Dalcin    docbody = textwrap.dedent(docbody).strip()
107e4efd8cbSStefano Zampini    if docbody and sig:
108e4efd8cbSStefano Zampini        if not summary.endswith('.'):
109e4efd8cbSStefano Zampini            logger.warning(f'Summary for {sig} does not end with period.')
110e4efd8cbSStefano Zampini        if len(summary) > 79:
111e4efd8cbSStefano Zampini            logger.warning(f'Summary for {sig} too long.')
112e4efd8cbSStefano Zampini        # FIXME
113*0898713fSStefano Zampini        lines = docbody.split('\n')
114*0898713fSStefano Zampini        for i,l in enumerate(lines):
115*0898713fSStefano Zampini            if len(l) > 79:
116*0898713fSStefano Zampini                logger.warning(f'Line {i} for {sig} too long.')
117e4efd8cbSStefano Zampini        #init = ("Collective.", "Not collective.", "Logically collective.", "Neighborwise collective.")
118e4efd8cbSStefano Zampini        #if lines[0] not in init:
119e4efd8cbSStefano Zampini        #   logger.warning(f'Unexpected collectiveness specification for {sig}\nFound {lines[0]}')
120e4efd8cbSStefano Zampini
121b3f8c7a1SStefano Zampini    if link:
122e4efd8cbSStefano Zampini        linktxt, _, link = link.rpartition(' ')
123b3f8c7a1SStefano Zampini        linkloc = link.replace(':','#L')
124e4efd8cbSStefano Zampini        # FIXME do we want to use a special section?
125b3f8c7a1SStefano Zampini        # section = f'References\n----------`'
126e4efd8cbSStefano Zampini        section = '\n'
127e4efd8cbSStefano Zampini        linkbody = f':sources:`{linktxt} {link} <{linkloc}>`'
128e4efd8cbSStefano Zampini        linkbody = f'{section}\n{linkbody}'
129b3f8c7a1SStefano Zampini        if docbody:
130b3f8c7a1SStefano Zampini            docbody = f'{docbody}\n\n{linkbody}'
131b3f8c7a1SStefano Zampini        else:
132b3f8c7a1SStefano Zampini            docbody = linkbody
133b3f8c7a1SStefano Zampini
13455a74a43SLisandro Dalcin    if docbody:
13555a74a43SLisandro Dalcin        doc = f'"""{summary}\n\n{docbody}\n\n"""'
13655a74a43SLisandro Dalcin    else:
13755a74a43SLisandro Dalcin        doc = f'"""{summary}"""'
13855a74a43SLisandro Dalcin    doc = textwrap.indent(doc, Lines.INDENT)
13955a74a43SLisandro Dalcin    return doc
14055a74a43SLisandro Dalcin
14155a74a43SLisandro Dalcin
14255a74a43SLisandro Dalcindef visit_data(constant):
14355a74a43SLisandro Dalcin    name, value = constant
14455a74a43SLisandro Dalcin    typename = type(value).__name__
14555a74a43SLisandro Dalcin    kind = "Constant" if isinstance(value, int) else "Object"
14655a74a43SLisandro Dalcin    init = f"_def({typename}, '{name}')"
14755a74a43SLisandro Dalcin    doc = f"#: {kind} ``{name}`` of type :class:`{typename}`"
14855a74a43SLisandro Dalcin    return f"{name}: {typename} = {init}  {doc}\n"
14955a74a43SLisandro Dalcin
15055a74a43SLisandro Dalcin
15155a74a43SLisandro Dalcindef visit_function(function):
15255a74a43SLisandro Dalcin    sig = signature(function)
15355a74a43SLisandro Dalcin    doc = docstring(function)
15455a74a43SLisandro Dalcin    body = Lines.INDENT + "..."
15555a74a43SLisandro Dalcin    return f"def {sig}:\n{doc}\n{body}\n"
15655a74a43SLisandro Dalcin
15755a74a43SLisandro Dalcin
15855a74a43SLisandro Dalcindef visit_method(method):
15955a74a43SLisandro Dalcin    sig = signature(method)
16055a74a43SLisandro Dalcin    doc = docstring(method)
16155a74a43SLisandro Dalcin    body = Lines.INDENT + "..."
16255a74a43SLisandro Dalcin    return f"def {sig}:\n{doc}\n{body}\n"
16355a74a43SLisandro Dalcin
16455a74a43SLisandro Dalcin
16555a74a43SLisandro Dalcindef visit_datadescr(datadescr, name=None):
16655a74a43SLisandro Dalcin    sig = signature(datadescr)
16755a74a43SLisandro Dalcin    doc = docstring(datadescr)
16855a74a43SLisandro Dalcin    name = sig.partition(':')[0].strip() or datadescr.__name__
16955a74a43SLisandro Dalcin    type = sig.partition(':')[2].strip() or 'Any'
17055a74a43SLisandro Dalcin    sig = f"{name}(self) -> {type}"
17155a74a43SLisandro Dalcin    body = Lines.INDENT + "..."
17255a74a43SLisandro Dalcin    return f"@property\ndef {sig}:\n{doc}\n{body}\n"
17355a74a43SLisandro Dalcin
17455a74a43SLisandro Dalcin
17555a74a43SLisandro Dalcindef visit_property(prop, name=None):
17655a74a43SLisandro Dalcin    sig = signature(prop.fget)
17755a74a43SLisandro Dalcin    name = name or prop.fget.__name__
17855a74a43SLisandro Dalcin    type = sig.rsplit('->', 1)[-1].strip()
17955a74a43SLisandro Dalcin    sig = f"{name}(self) -> {type}"
18055a74a43SLisandro Dalcin    doc = f'"""{prop.__doc__}"""'
18155a74a43SLisandro Dalcin    doc = textwrap.indent(doc, Lines.INDENT)
18255a74a43SLisandro Dalcin    body = Lines.INDENT + "..."
18355a74a43SLisandro Dalcin    return f"@property\ndef {sig}:\n{doc}\n{body}\n"
18455a74a43SLisandro Dalcin
18555a74a43SLisandro Dalcin
18655a74a43SLisandro Dalcindef visit_constructor(cls, name='__init__', args=None):
18755a74a43SLisandro Dalcin    init = (name == '__init__')
18855a74a43SLisandro Dalcin    argname = cls.__mro__[-2].__name__.lower()
18955a74a43SLisandro Dalcin    argtype = cls.__name__
19055a74a43SLisandro Dalcin    initarg = args or f"{argname}: Optional[{argtype}] = None"
19155a74a43SLisandro Dalcin    selfarg = 'self' if init else 'cls'
19255a74a43SLisandro Dalcin    rettype = 'None' if init else argtype
19355a74a43SLisandro Dalcin    arglist = f"{selfarg}, {initarg}"
19455a74a43SLisandro Dalcin    sig = f"{name}({arglist}) -> {rettype}"
19555a74a43SLisandro Dalcin    ret = '...' if init else 'return super().__new__(cls)'
19655a74a43SLisandro Dalcin    body = Lines.INDENT + ret
19755a74a43SLisandro Dalcin    return f"def {sig}:\n{body}"
19855a74a43SLisandro Dalcin
19955a74a43SLisandro Dalcin
20055a74a43SLisandro Dalcindef visit_class(cls, outer=None, done=None):
20155a74a43SLisandro Dalcin    skip = {
20255a74a43SLisandro Dalcin        '__doc__',
20355a74a43SLisandro Dalcin        '__dict__',
20455a74a43SLisandro Dalcin        '__module__',
20555a74a43SLisandro Dalcin        '__weakref__',
20655a74a43SLisandro Dalcin        '__pyx_vtable__',
20755a74a43SLisandro Dalcin        '__lt__',
20855a74a43SLisandro Dalcin        '__le__',
20955a74a43SLisandro Dalcin        '__ge__',
21055a74a43SLisandro Dalcin        '__gt__',
21155a74a43SLisandro Dalcin        '__enum2str',  # FIXME refactor implemetation
21255a74a43SLisandro Dalcin        '_traceback_', # FIXME maybe refactor?
21355a74a43SLisandro Dalcin    }
21455a74a43SLisandro Dalcin    special = {
21555a74a43SLisandro Dalcin        '__len__': "__len__(self) -> int",
21655a74a43SLisandro Dalcin        '__bool__': "__bool__(self) -> bool",
21755a74a43SLisandro Dalcin        '__hash__': "__hash__(self) -> int",
21855a74a43SLisandro Dalcin        '__int__': "__int__(self) -> int",
21955a74a43SLisandro Dalcin        '__index__': "__int__(self) -> int",
22055a74a43SLisandro Dalcin        '__str__': "__str__(self) -> str",
22155a74a43SLisandro Dalcin        '__repr__': "__repr__(self) -> str",
22255a74a43SLisandro Dalcin        '__eq__': "__eq__(self, other: object) -> bool",
22355a74a43SLisandro Dalcin        '__ne__': "__ne__(self, other: object) -> bool",
22455a74a43SLisandro Dalcin    }
22555a74a43SLisandro Dalcin    constructor = (
22655a74a43SLisandro Dalcin        '__new__',
22755a74a43SLisandro Dalcin        '__init__',
22855a74a43SLisandro Dalcin    )
22955a74a43SLisandro Dalcin
23055a74a43SLisandro Dalcin    qualname = cls.__name__
23155a74a43SLisandro Dalcin    cls_name = cls.__name__
23255a74a43SLisandro Dalcin    if outer is not None and cls_name.startswith(outer):
23355a74a43SLisandro Dalcin        cls_name = cls_name[len(outer):]
23455a74a43SLisandro Dalcin        qualname = f"{outer}.{cls_name}"
23555a74a43SLisandro Dalcin
23655a74a43SLisandro Dalcin    override = OVERRIDE.get(qualname, {})
23755a74a43SLisandro Dalcin    done = set() if done is None else done
23855a74a43SLisandro Dalcin    lines = Lines()
23955a74a43SLisandro Dalcin
24055a74a43SLisandro Dalcin    base = cls.__base__
24155a74a43SLisandro Dalcin    if base is object:
24255a74a43SLisandro Dalcin        lines.add = f"class {cls_name}:"
24355a74a43SLisandro Dalcin    else:
24455a74a43SLisandro Dalcin        lines.add = f"class {cls_name}({base.__name__}):"
24555a74a43SLisandro Dalcin    lines.level += 1
24655a74a43SLisandro Dalcin
24755a74a43SLisandro Dalcin    lines.add = docstring(cls)
24855a74a43SLisandro Dalcin
24955a74a43SLisandro Dalcin    for name in ('__new__', '__init__', '__hash__'):
25055a74a43SLisandro Dalcin        if name in cls.__dict__:
25155a74a43SLisandro Dalcin            done.add(name)
25255a74a43SLisandro Dalcin
25355a74a43SLisandro Dalcin    dct = cls.__dict__
25455a74a43SLisandro Dalcin    keys = list(dct.keys())
25555a74a43SLisandro Dalcin
25655a74a43SLisandro Dalcin    def dunder(name):
25755a74a43SLisandro Dalcin        return name.startswith('__') and name.endswith('__')
25855a74a43SLisandro Dalcin
25955a74a43SLisandro Dalcin    def members(seq):
26055a74a43SLisandro Dalcin        for name in seq:
26155a74a43SLisandro Dalcin            if name in skip:
26255a74a43SLisandro Dalcin                continue
26355a74a43SLisandro Dalcin            if name in done:
26455a74a43SLisandro Dalcin                continue
26555a74a43SLisandro Dalcin            if dunder(name):
26655a74a43SLisandro Dalcin                if name not in special and name not in override:
26755a74a43SLisandro Dalcin                    done.add(name)
26855a74a43SLisandro Dalcin                    continue
26955a74a43SLisandro Dalcin            yield name
27055a74a43SLisandro Dalcin
27155a74a43SLisandro Dalcin    for name in members(keys):
27255a74a43SLisandro Dalcin        attr = getattr(cls, name)
27355a74a43SLisandro Dalcin        if is_class(attr):
27455a74a43SLisandro Dalcin            done.add(name)
27555a74a43SLisandro Dalcin            lines.add = visit_class(attr, outer=cls_name)
27655a74a43SLisandro Dalcin            continue
27755a74a43SLisandro Dalcin
27855a74a43SLisandro Dalcin    for name in members(keys):
27955a74a43SLisandro Dalcin
28055a74a43SLisandro Dalcin        if name in override:
28155a74a43SLisandro Dalcin            done.add(name)
28255a74a43SLisandro Dalcin            lines.add = override[name]
28355a74a43SLisandro Dalcin            continue
28455a74a43SLisandro Dalcin
28555a74a43SLisandro Dalcin        if name in special:
28655a74a43SLisandro Dalcin            done.add(name)
28755a74a43SLisandro Dalcin            sig = special[name]
28855a74a43SLisandro Dalcin            lines.add = f"def {sig}: ..."
28955a74a43SLisandro Dalcin            continue
29055a74a43SLisandro Dalcin
29155a74a43SLisandro Dalcin        attr = getattr(cls, name)
29255a74a43SLisandro Dalcin
29355a74a43SLisandro Dalcin        if is_method(attr):
29455a74a43SLisandro Dalcin            done.add(name)
29555a74a43SLisandro Dalcin            if name == attr.__name__:
29655a74a43SLisandro Dalcin                obj = dct[name]
29755a74a43SLisandro Dalcin                if is_classmethod(obj):
29855a74a43SLisandro Dalcin                    lines.add = "@classmethod"
29955a74a43SLisandro Dalcin                elif is_staticmethod(obj):
30055a74a43SLisandro Dalcin                    lines.add = "@staticmethod"
30155a74a43SLisandro Dalcin                lines.add = visit_method(attr)
30255a74a43SLisandro Dalcin            elif False:
30355a74a43SLisandro Dalcin                lines.add = f"{name} = {attr.__name__}"
30455a74a43SLisandro Dalcin            continue
30555a74a43SLisandro Dalcin
30655a74a43SLisandro Dalcin        if is_datadescr(attr):
30755a74a43SLisandro Dalcin            done.add(name)
30855a74a43SLisandro Dalcin            lines.add = visit_datadescr(attr)
30955a74a43SLisandro Dalcin            continue
31055a74a43SLisandro Dalcin
31155a74a43SLisandro Dalcin        if is_property(attr):
31255a74a43SLisandro Dalcin            done.add(name)
31355a74a43SLisandro Dalcin            lines.add = visit_property(attr, name)
31455a74a43SLisandro Dalcin            continue
31555a74a43SLisandro Dalcin
31655a74a43SLisandro Dalcin        if is_constant(attr):
31755a74a43SLisandro Dalcin            done.add(name)
31855a74a43SLisandro Dalcin            lines.add = visit_data((name, attr))
31955a74a43SLisandro Dalcin            continue
32055a74a43SLisandro Dalcin
32155a74a43SLisandro Dalcin    leftovers = [name for name in keys if
32255a74a43SLisandro Dalcin                 name not in done and name not in skip]
32355a74a43SLisandro Dalcin    if leftovers:
32455a74a43SLisandro Dalcin        raise RuntimeError(f"leftovers: {leftovers}")
32555a74a43SLisandro Dalcin
32655a74a43SLisandro Dalcin    lines.level -= 1
32755a74a43SLisandro Dalcin    return lines
32855a74a43SLisandro Dalcin
32955a74a43SLisandro Dalcin
33055a74a43SLisandro Dalcindef visit_module(module, done=None):
33155a74a43SLisandro Dalcin    skip = {
33255a74a43SLisandro Dalcin        '__doc__',
33355a74a43SLisandro Dalcin        '__name__',
33455a74a43SLisandro Dalcin        '__loader__',
33555a74a43SLisandro Dalcin        '__spec__',
33655a74a43SLisandro Dalcin        '__file__',
33755a74a43SLisandro Dalcin        '__package__',
33855a74a43SLisandro Dalcin        '__builtins__',
33955a74a43SLisandro Dalcin        '__pyx_capi__',
34055a74a43SLisandro Dalcin        '__pyx_unpickle_Enum',  # FIXME review
34155a74a43SLisandro Dalcin    }
34255a74a43SLisandro Dalcin
34355a74a43SLisandro Dalcin    done = set() if done is None else done
34455a74a43SLisandro Dalcin    lines = Lines()
34555a74a43SLisandro Dalcin
34655a74a43SLisandro Dalcin    keys = list(module.__dict__.keys())
34755a74a43SLisandro Dalcin    keys.sort(key=lambda name: name.startswith("_"))
34855a74a43SLisandro Dalcin
34955a74a43SLisandro Dalcin    constants = [
35055a74a43SLisandro Dalcin        (name, getattr(module, name)) for name in keys
35155a74a43SLisandro Dalcin        if all((
35255a74a43SLisandro Dalcin            name not in done and name not in skip,
35355a74a43SLisandro Dalcin            is_constant(getattr(module, name)),
35455a74a43SLisandro Dalcin        ))
35555a74a43SLisandro Dalcin    ]
35655a74a43SLisandro Dalcin    for _, value in constants:
35755a74a43SLisandro Dalcin        cls = type(value)
35855a74a43SLisandro Dalcin        name = cls.__name__
35955a74a43SLisandro Dalcin        if name in done or name in skip:
36055a74a43SLisandro Dalcin            continue
36155a74a43SLisandro Dalcin        if cls.__module__ == module.__name__:
36255a74a43SLisandro Dalcin            done.add(name)
36355a74a43SLisandro Dalcin            lines.add = visit_class(cls)
36455a74a43SLisandro Dalcin            lines.add = ""
36555a74a43SLisandro Dalcin    for attr in constants:
36655a74a43SLisandro Dalcin        name, value = attr
36755a74a43SLisandro Dalcin        done.add(name)
36855a74a43SLisandro Dalcin        if name in OVERRIDE:
36955a74a43SLisandro Dalcin            lines.add = OVERRIDE[name]
37055a74a43SLisandro Dalcin        else:
37155a74a43SLisandro Dalcin            lines.add = visit_data((name, value))
37255a74a43SLisandro Dalcin    if constants:
37355a74a43SLisandro Dalcin        lines.add = ""
37455a74a43SLisandro Dalcin
37555a74a43SLisandro Dalcin    for name in keys:
37655a74a43SLisandro Dalcin        if name in done or name in skip:
37755a74a43SLisandro Dalcin            continue
37855a74a43SLisandro Dalcin        value = getattr(module, name)
37955a74a43SLisandro Dalcin
38055a74a43SLisandro Dalcin        if is_class(value):
38155a74a43SLisandro Dalcin            done.add(name)
38255a74a43SLisandro Dalcin            if value.__name__ != name:
38355a74a43SLisandro Dalcin                continue
38455a74a43SLisandro Dalcin            if value.__module__ != module.__name__:
38555a74a43SLisandro Dalcin                continue
38655a74a43SLisandro Dalcin            lines.add = visit_class(value)
38755a74a43SLisandro Dalcin            lines.add = ""
38855a74a43SLisandro Dalcin            instances = [
38955a74a43SLisandro Dalcin                (k, getattr(module, k)) for k in keys
39055a74a43SLisandro Dalcin                if all((
39155a74a43SLisandro Dalcin                    k not in done and k not in skip,
39255a74a43SLisandro Dalcin                    type(getattr(module, k)) is value,
39355a74a43SLisandro Dalcin                ))
39455a74a43SLisandro Dalcin            ]
39555a74a43SLisandro Dalcin            for attrname, attrvalue in instances:
39655a74a43SLisandro Dalcin                done.add(attrname)
39755a74a43SLisandro Dalcin                lines.add = visit_data((attrname, attrvalue))
39855a74a43SLisandro Dalcin            if instances:
39955a74a43SLisandro Dalcin                lines.add = ""
40055a74a43SLisandro Dalcin            continue
40155a74a43SLisandro Dalcin
40255a74a43SLisandro Dalcin        if is_function(value):
40355a74a43SLisandro Dalcin            done.add(name)
40455a74a43SLisandro Dalcin            if name == value.__name__:
40555a74a43SLisandro Dalcin                lines.add = visit_function(value)
40655a74a43SLisandro Dalcin            else:
40755a74a43SLisandro Dalcin                lines.add = f"{name} = {value.__name__}"
40855a74a43SLisandro Dalcin            continue
40955a74a43SLisandro Dalcin
41055a74a43SLisandro Dalcin    lines.add = ""
41155a74a43SLisandro Dalcin    for name in keys:
41255a74a43SLisandro Dalcin        if name in done or name in skip:
41355a74a43SLisandro Dalcin            continue
41455a74a43SLisandro Dalcin        value = getattr(module, name)
41555a74a43SLisandro Dalcin        done.add(name)
41655a74a43SLisandro Dalcin        if name in OVERRIDE:
41755a74a43SLisandro Dalcin            lines.add = OVERRIDE[name]
41855a74a43SLisandro Dalcin        else:
41955a74a43SLisandro Dalcin            lines.add = visit_data((name, value))
42055a74a43SLisandro Dalcin
42155a74a43SLisandro Dalcin    leftovers = [name for name in keys if
42255a74a43SLisandro Dalcin                 name not in done and name not in skip]
42355a74a43SLisandro Dalcin    if leftovers:
42455a74a43SLisandro Dalcin        raise RuntimeError(f"leftovers: {leftovers}")
42555a74a43SLisandro Dalcin    return lines
42655a74a43SLisandro Dalcin
42755a74a43SLisandro Dalcin
42855a74a43SLisandro DalcinIMPORTS = """
42955a74a43SLisandro Dalcinfrom __future__ import annotations
43055a74a43SLisandro Dalcinimport sys
43155a74a43SLisandro Dalcinfrom typing import (
43255a74a43SLisandro Dalcin    Any,
43355a74a43SLisandro Dalcin    Union,
43455a74a43SLisandro Dalcin    Literal,
43555a74a43SLisandro Dalcin    Optional,
43655a74a43SLisandro Dalcin    NoReturn,
43755a74a43SLisandro Dalcin    Final,
43855a74a43SLisandro Dalcin)
43955a74a43SLisandro Dalcinfrom typing import (
44055a74a43SLisandro Dalcin    Callable,
44155a74a43SLisandro Dalcin    Hashable,
44255a74a43SLisandro Dalcin    Iterable,
44355a74a43SLisandro Dalcin    Iterator,
44455a74a43SLisandro Dalcin    Sequence,
44555a74a43SLisandro Dalcin    Mapping,
44655a74a43SLisandro Dalcin)
44755a74a43SLisandro Dalcinif sys.version_info >= (3, 11):
44855a74a43SLisandro Dalcin    from typing import Self
44955a74a43SLisandro Dalcinelse:
45055a74a43SLisandro Dalcin    from typing_extensions import Self
45155a74a43SLisandro Dalcin
45255a74a43SLisandro Dalcinimport numpy
45355a74a43SLisandro Dalcinfrom numpy import dtype, ndarray
45455a74a43SLisandro Dalcinfrom mpi4py.MPI import (
45555a74a43SLisandro Dalcin    Intracomm,
45655a74a43SLisandro Dalcin    Datatype,
45755a74a43SLisandro Dalcin    Op,
45855a74a43SLisandro Dalcin)
45955a74a43SLisandro Dalcin
46055a74a43SLisandro Dalcinclass _dtype:
46155a74a43SLisandro Dalcin    def __init__(self, name):
46255a74a43SLisandro Dalcin        self.name = name
46355a74a43SLisandro Dalcin    def __repr__(self):
46455a74a43SLisandro Dalcin        return self.name
46555a74a43SLisandro Dalcin
46655a74a43SLisandro DalcinIntType: dtype = _dtype('IntType')
46755a74a43SLisandro DalcinRealType: dtype =  _dtype('RealType')
46855a74a43SLisandro DalcinComplexType: dtype = _dtype('ComplexType')
46955a74a43SLisandro DalcinScalarType: dtype = _dtype('ScalarType')
47055a74a43SLisandro Dalcin"""
47155a74a43SLisandro Dalcin
47255a74a43SLisandro DalcinHELPERS = """
47355a74a43SLisandro Dalcinclass _Int(int): pass
47455a74a43SLisandro Dalcinclass _Str(str): pass
47555a74a43SLisandro Dalcinclass _Float(float): pass
47655a74a43SLisandro Dalcinclass _Dict(dict): pass
47755a74a43SLisandro Dalcin
47855a74a43SLisandro Dalcindef _repr(obj):
47955a74a43SLisandro Dalcin    try:
48055a74a43SLisandro Dalcin        return obj._name
48155a74a43SLisandro Dalcin    except AttributeError:
48255a74a43SLisandro Dalcin        return super(obj).__repr__()
48355a74a43SLisandro Dalcin
48455a74a43SLisandro Dalcindef _def(cls, name):
48555a74a43SLisandro Dalcin    if cls is int:
48655a74a43SLisandro Dalcin       cls = _Int
48755a74a43SLisandro Dalcin    if cls is str:
48855a74a43SLisandro Dalcin       cls = _Str
48955a74a43SLisandro Dalcin    if cls is float:
49055a74a43SLisandro Dalcin       cls = _Float
49155a74a43SLisandro Dalcin    if cls is dict:
49255a74a43SLisandro Dalcin       cls = _Dict
49355a74a43SLisandro Dalcin
49455a74a43SLisandro Dalcin    obj = cls()
49555a74a43SLisandro Dalcin    obj._name = name
49655a74a43SLisandro Dalcin    if '__repr__' not in cls.__dict__:
49755a74a43SLisandro Dalcin        cls.__repr__ = _repr
49855a74a43SLisandro Dalcin    return obj
49955a74a43SLisandro Dalcin"""
50055a74a43SLisandro Dalcin
50155a74a43SLisandro DalcinOVERRIDE = {
50255a74a43SLisandro Dalcin}
50355a74a43SLisandro Dalcin
50455a74a43SLisandro DalcinTYPING = """
50555a74a43SLisandro Dalcinfrom .typing import *
50655a74a43SLisandro Dalcin"""
50755a74a43SLisandro Dalcin
50855a74a43SLisandro Dalcin
50955a74a43SLisandro Dalcindef visit_petsc4py_PETSc(done=None):
51055a74a43SLisandro Dalcin    from petsc4py import PETSc
51155a74a43SLisandro Dalcin    lines = Lines()
51255a74a43SLisandro Dalcin    lines.add = f'"""{PETSc.__doc__}"""'
51355a74a43SLisandro Dalcin    lines.add = IMPORTS
51455a74a43SLisandro Dalcin    lines.add = ""
51555a74a43SLisandro Dalcin    lines.add = HELPERS
51655a74a43SLisandro Dalcin    lines.add = ""
51755a74a43SLisandro Dalcin    lines.add = visit_module(PETSc)
51855a74a43SLisandro Dalcin    lines.add = ""
51955a74a43SLisandro Dalcin    lines.add = TYPING
52055a74a43SLisandro Dalcin    return lines
52155a74a43SLisandro Dalcin
52255a74a43SLisandro Dalcin
52355a74a43SLisandro Dalcindef generate(filename):
52455a74a43SLisandro Dalcin    dirname = os.path.dirname(filename)
52555a74a43SLisandro Dalcin    os.makedirs(dirname, exist_ok=True)
52655a74a43SLisandro Dalcin    with open(filename, 'w') as f:
52755a74a43SLisandro Dalcin        for line in visit_petsc4py_PETSc():
52855a74a43SLisandro Dalcin            print(line, file=f)
52955a74a43SLisandro Dalcin
53055a74a43SLisandro Dalcin
53155a74a43SLisandro Dalcindef load_module(filename, name=None):
53255a74a43SLisandro Dalcin    if name is None:
53355a74a43SLisandro Dalcin        name, _ = os.path.splitext(
53455a74a43SLisandro Dalcin            os.path.basename(filename))
53555a74a43SLisandro Dalcin    module = type(sys)(name)
53655a74a43SLisandro Dalcin    module.__file__ = filename
53755a74a43SLisandro Dalcin    module.__package__ = name.rsplit('.', 1)[0]
53855a74a43SLisandro Dalcin    old = replace_module(module)
53955a74a43SLisandro Dalcin    with open(filename) as f:
54055a74a43SLisandro Dalcin        exec(f.read(), module.__dict__)  # noqa: S102
54155a74a43SLisandro Dalcin    restore_module(old)
54255a74a43SLisandro Dalcin    return module
54355a74a43SLisandro Dalcin
54455a74a43SLisandro Dalcin
54555a74a43SLisandro Dalcin_sys_modules = {}
54655a74a43SLisandro Dalcin
54755a74a43SLisandro Dalcin
54855a74a43SLisandro Dalcindef replace_module(module):
54955a74a43SLisandro Dalcin    name = module.__name__
55055a74a43SLisandro Dalcin    assert name not in _sys_modules
55155a74a43SLisandro Dalcin    _sys_modules[name] = sys.modules[name]
55255a74a43SLisandro Dalcin    sys.modules[name] = module
55355a74a43SLisandro Dalcin    return _sys_modules[name]
55455a74a43SLisandro Dalcin
55555a74a43SLisandro Dalcin
55655a74a43SLisandro Dalcindef restore_module(module):
55755a74a43SLisandro Dalcin    name = module.__name__
55855a74a43SLisandro Dalcin    assert name in _sys_modules
55955a74a43SLisandro Dalcin    sys.modules[name] = _sys_modules[name]
56055a74a43SLisandro Dalcin    del _sys_modules[name]
56155a74a43SLisandro Dalcin
56255a74a43SLisandro Dalcin
56355a74a43SLisandro Dalcindef annotate(dest, source):
56455a74a43SLisandro Dalcin    try:
56555a74a43SLisandro Dalcin        dest.__annotations__ = source.__annotations__
56655a74a43SLisandro Dalcin    except AttributeError:
56755a74a43SLisandro Dalcin        pass
56855a74a43SLisandro Dalcin    if isinstance(dest, type):
56955a74a43SLisandro Dalcin        for name in dest.__dict__.keys():
57055a74a43SLisandro Dalcin            if hasattr(source, name):
57155a74a43SLisandro Dalcin                obj = getattr(dest, name)
57255a74a43SLisandro Dalcin                annotate(obj, getattr(source, name))
57355a74a43SLisandro Dalcin    if isinstance(dest, type(sys)):
57455a74a43SLisandro Dalcin        for name in dir(dest):
57555a74a43SLisandro Dalcin            if hasattr(source, name):
57655a74a43SLisandro Dalcin                obj = getattr(dest, name)
57755a74a43SLisandro Dalcin                mod = getattr(obj, '__module__', None)
57855a74a43SLisandro Dalcin                if dest.__name__ == mod:
57955a74a43SLisandro Dalcin                    annotate(obj, getattr(source, name))
58055a74a43SLisandro Dalcin        for name in dir(source):
58155a74a43SLisandro Dalcin            if not hasattr(dest, name):
58255a74a43SLisandro Dalcin                setattr(dest, name, getattr(source, name))
58355a74a43SLisandro Dalcin
58455a74a43SLisandro Dalcin
58555a74a43SLisandro DalcinOUTDIR = 'reference'
58655a74a43SLisandro Dalcin
58755a74a43SLisandro Dalcinif __name__ == '__main__':
58855a74a43SLisandro Dalcin    generate(os.path.join(OUTDIR, 'petsc4py.PETSc.py'))
589