xref: /petsc/src/binding/petsc4py/docs/source/apidoc.py (revision 552edb6364df478b294b3111f33a8f37ca096b20)
155a74a43SLisandro Dalcinimport os
255a74a43SLisandro Dalcinimport sys
355a74a43SLisandro Dalcinimport inspect
455a74a43SLisandro Dalcinimport textwrap
5e4efd8cbSStefano Zampinifrom sphinx.util import logging
6*6f336411SStefano Zampini
7e4efd8cbSStefano Zampinilogger = logging.getLogger(__name__)
855a74a43SLisandro Dalcin
9*6f336411SStefano Zampini
1055a74a43SLisandro Dalcindef is_cyfunction(obj):
1155a74a43SLisandro Dalcin    return type(obj).__name__ == 'cython_function_or_method'
1255a74a43SLisandro Dalcin
1355a74a43SLisandro Dalcin
1455a74a43SLisandro Dalcindef is_function(obj):
15*6f336411SStefano Zampini    return inspect.isbuiltin(obj) or is_cyfunction(obj) or type(obj) is type(ord)
1655a74a43SLisandro Dalcin
1755a74a43SLisandro Dalcin
1855a74a43SLisandro Dalcindef is_method(obj):
1955a74a43SLisandro Dalcin    return (
2055a74a43SLisandro Dalcin        inspect.ismethoddescriptor(obj)
2155a74a43SLisandro Dalcin        or inspect.ismethod(obj)
2255a74a43SLisandro Dalcin        or is_cyfunction(obj)
23*6f336411SStefano Zampini        or type(obj)
24*6f336411SStefano Zampini        in (
2555a74a43SLisandro Dalcin            type(str.index),
2655a74a43SLisandro Dalcin            type(str.__add__),
2755a74a43SLisandro Dalcin            type(str.__new__),
2855a74a43SLisandro Dalcin        )
2955a74a43SLisandro Dalcin    )
3055a74a43SLisandro Dalcin
3155a74a43SLisandro Dalcin
3255a74a43SLisandro Dalcindef is_classmethod(obj):
33*6f336411SStefano Zampini    return inspect.isbuiltin(obj) or type(obj).__name__ in (
3455a74a43SLisandro Dalcin        'classmethod',
3555a74a43SLisandro Dalcin        'classmethod_descriptor',
3655a74a43SLisandro Dalcin    )
3755a74a43SLisandro Dalcin
3855a74a43SLisandro Dalcin
3955a74a43SLisandro Dalcindef is_staticmethod(obj):
40*6f336411SStefano Zampini    return type(obj).__name__ in ('staticmethod',)
41*6f336411SStefano Zampini
4255a74a43SLisandro Dalcin
4355a74a43SLisandro Dalcindef is_constant(obj):
4455a74a43SLisandro Dalcin    return isinstance(obj, (int, float, str, dict))
4555a74a43SLisandro Dalcin
46*6f336411SStefano Zampini
4755a74a43SLisandro Dalcindef is_datadescr(obj):
4855a74a43SLisandro Dalcin    return inspect.isdatadescriptor(obj) and not hasattr(obj, 'fget')
4955a74a43SLisandro Dalcin
5055a74a43SLisandro Dalcin
5155a74a43SLisandro Dalcindef is_property(obj):
5255a74a43SLisandro Dalcin    return inspect.isdatadescriptor(obj) and hasattr(obj, 'fget')
5355a74a43SLisandro Dalcin
5455a74a43SLisandro Dalcin
5555a74a43SLisandro Dalcindef is_class(obj):
5655a74a43SLisandro Dalcin    return inspect.isclass(obj) or type(obj) is type(int)
5755a74a43SLisandro Dalcin
5855a74a43SLisandro Dalcin
59*6f336411SStefano Zampinidef is_hidden(obj):
60*6f336411SStefano Zampini    return obj.__qualname__.startswith('_')
6155a74a43SLisandro Dalcin
62*6f336411SStefano Zampini
63*6f336411SStefano Zampiniclass Lines(list):
64*6f336411SStefano Zampini    INDENT = ' ' * 4
6555a74a43SLisandro Dalcin    level = 0
6655a74a43SLisandro Dalcin
6755a74a43SLisandro Dalcin    @property
6855a74a43SLisandro Dalcin    def add(self):
6955a74a43SLisandro Dalcin        return self
7055a74a43SLisandro Dalcin
7155a74a43SLisandro Dalcin    @add.setter
7255a74a43SLisandro Dalcin    def add(self, lines):
7355a74a43SLisandro Dalcin        if lines is None:
7455a74a43SLisandro Dalcin            return
7555a74a43SLisandro Dalcin        if isinstance(lines, str):
7655a74a43SLisandro Dalcin            lines = textwrap.dedent(lines).strip().split('\n')
7755a74a43SLisandro Dalcin        indent = self.INDENT * self.level
7855a74a43SLisandro Dalcin        for line in lines:
7955a74a43SLisandro Dalcin            self.append(indent + line)
8055a74a43SLisandro Dalcin
8155a74a43SLisandro Dalcin
82*6f336411SStefano Zampinidef signature(obj, fail=True):
8355a74a43SLisandro Dalcin    doc = obj.__doc__
84*6f336411SStefano Zampini    if not doc:
85*6f336411SStefano Zampini        if fail and not is_hidden(obj):
86*6f336411SStefano Zampini            logger.warning(f'Missing signature for {obj}')
87*6f336411SStefano Zampini        doc = f'{obj.__name__}: Any'
8855a74a43SLisandro Dalcin    sig = doc.partition('\n')[0].split('.', 1)[-1]
8955a74a43SLisandro Dalcin    return sig or None
9055a74a43SLisandro Dalcin
9155a74a43SLisandro Dalcin
92*6f336411SStefano Zampinidef docstring(obj, fail=True):
9355a74a43SLisandro Dalcin    doc = obj.__doc__
94*6f336411SStefano Zampini    if not doc:
95*6f336411SStefano Zampini        if fail and not is_hidden(obj):
96*6f336411SStefano Zampini            logger.warning(f'Missing docstring for {obj}')
97*6f336411SStefano Zampini        doc = ''
98b3f8c7a1SStefano Zampini    link = None
99e4efd8cbSStefano Zampini    sig = None
100e4efd8cbSStefano Zampini    cl = is_class(obj)
101e4efd8cbSStefano Zampini    if cl:
10255a74a43SLisandro Dalcin        doc = doc.strip()
10355a74a43SLisandro Dalcin    else:
104e4efd8cbSStefano Zampini        sig, _, doc = doc.partition('\n')
105b3f8c7a1SStefano Zampini        doc, _, link = doc.rpartition('\n')
106b3f8c7a1SStefano Zampini
10755a74a43SLisandro Dalcin    summary, _, docbody = doc.partition('\n')
10855a74a43SLisandro Dalcin    summary = summary.strip()
10955a74a43SLisandro Dalcin    docbody = textwrap.dedent(docbody).strip()
110*6f336411SStefano Zampini
111*6f336411SStefano Zampini    # raise warning if docstring is not provided for a method
112*6f336411SStefano Zampini    if not summary and not is_function(obj) and is_method(obj):
113*6f336411SStefano Zampini        logger.warning(f'docstring: Missing summary for {obj}')
114*6f336411SStefano Zampini
115*6f336411SStefano Zampini    # warnings for docstrings that are not compliant
116e4efd8cbSStefano Zampini    if len(summary) > 79:
117*6f336411SStefano Zampini        logger.warning(f'Summary for {obj} too long.')
118*6f336411SStefano Zampini    if docbody:
119*6f336411SStefano Zampini        if not summary.endswith('.'):
120*6f336411SStefano Zampini            logger.warning(f'Summary for {obj} does not end with period.')
121e4efd8cbSStefano Zampini        # FIXME
1220898713fSStefano Zampini        lines = docbody.split('\n')
123*6f336411SStefano Zampini        for i, line in enumerate(lines):
124*6f336411SStefano Zampini            if len(line) > 79:
125*6f336411SStefano Zampini                logger.warning(f'Line {i} for documentation of {obj} too long.')
126*6f336411SStefano Zampini        if not cl:
127*6f336411SStefano Zampini            init = (
128*6f336411SStefano Zampini                'Collective.',
129*6f336411SStefano Zampini                'Not collective.',
130*6f336411SStefano Zampini                'Logically collective.',
131*6f336411SStefano Zampini                'Neighborwise collective.',
132*6f336411SStefano Zampini                'Collective the first time it is called.',
133*6f336411SStefano Zampini            )
134*6f336411SStefano Zampini            if lines[0] not in init:
135*6f336411SStefano Zampini                logger.warning(f'Unexpected collectiveness for {sig}\nFound {lines[0]}')
136e4efd8cbSStefano Zampini
137b3f8c7a1SStefano Zampini    if link:
138e4efd8cbSStefano Zampini        linktxt, _, link = link.rpartition(' ')
139b3f8c7a1SStefano Zampini        linkloc = link.replace(':', '#L')
140e4efd8cbSStefano Zampini        # FIXME do we want to use a special section?
141b3f8c7a1SStefano Zampini        # section = f'References\n----------`'
142e4efd8cbSStefano Zampini        section = '\n'
143e4efd8cbSStefano Zampini        linkbody = f':sources:`{linktxt} {link} <{linkloc}>`'
144e4efd8cbSStefano Zampini        linkbody = f'{section}\n{linkbody}'
145b3f8c7a1SStefano Zampini        if docbody:
146b3f8c7a1SStefano Zampini            docbody = f'{docbody}\n\n{linkbody}'
147b3f8c7a1SStefano Zampini        else:
148b3f8c7a1SStefano Zampini            docbody = linkbody
149b3f8c7a1SStefano Zampini
15055a74a43SLisandro Dalcin    if docbody:
15155a74a43SLisandro Dalcin        doc = f'"""{summary}\n\n{docbody}\n\n"""'
15255a74a43SLisandro Dalcin    else:
15355a74a43SLisandro Dalcin        doc = f'"""{summary}"""'
154*6f336411SStefano Zampini    return textwrap.indent(doc, Lines.INDENT)
15555a74a43SLisandro Dalcin
15655a74a43SLisandro Dalcin
15755a74a43SLisandro Dalcindef visit_data(constant):
15855a74a43SLisandro Dalcin    name, value = constant
15955a74a43SLisandro Dalcin    typename = type(value).__name__
160*6f336411SStefano Zampini    kind = 'Constant' if isinstance(value, int) else 'Object'
16155a74a43SLisandro Dalcin    init = f"_def({typename}, '{name}')"
162*6f336411SStefano Zampini    doc = f'#: {kind} ``{name}`` of type :class:`{typename}`'
163*6f336411SStefano Zampini    return f'{name}: {typename} = {init}  {doc}\n'
16455a74a43SLisandro Dalcin
16555a74a43SLisandro Dalcin
16655a74a43SLisandro Dalcindef visit_function(function):
16755a74a43SLisandro Dalcin    sig = signature(function)
16855a74a43SLisandro Dalcin    doc = docstring(function)
169*6f336411SStefano Zampini    body = Lines.INDENT + '...'
170*6f336411SStefano Zampini    return f'def {sig}:\n{doc}\n{body}\n'
17155a74a43SLisandro Dalcin
17255a74a43SLisandro Dalcin
17355a74a43SLisandro Dalcindef visit_method(method):
17455a74a43SLisandro Dalcin    sig = signature(method)
17555a74a43SLisandro Dalcin    doc = docstring(method)
176*6f336411SStefano Zampini    body = Lines.INDENT + '...'
177*6f336411SStefano Zampini    return f'def {sig}:\n{doc}\n{body}\n'
17855a74a43SLisandro Dalcin
17955a74a43SLisandro Dalcin
18055a74a43SLisandro Dalcindef visit_datadescr(datadescr, name=None):
18155a74a43SLisandro Dalcin    sig = signature(datadescr)
18255a74a43SLisandro Dalcin    doc = docstring(datadescr)
18355a74a43SLisandro Dalcin    name = sig.partition(':')[0].strip() or datadescr.__name__
184*6f336411SStefano Zampini    rtype = sig.partition(':')[2].strip() or 'Any'
185*6f336411SStefano Zampini    sig = f'{name}(self) -> {rtype}'
186*6f336411SStefano Zampini    body = Lines.INDENT + '...'
187*6f336411SStefano Zampini    return f'@property\ndef {sig}:\n{doc}\n{body}\n'
18855a74a43SLisandro Dalcin
18955a74a43SLisandro Dalcin
19055a74a43SLisandro Dalcindef visit_property(prop, name=None):
19155a74a43SLisandro Dalcin    sig = signature(prop.fget)
19255a74a43SLisandro Dalcin    name = name or prop.fget.__name__
193*6f336411SStefano Zampini    rtype = sig.rsplit('->', 1)[-1].strip()
194*6f336411SStefano Zampini    sig = f'{name}(self) -> {rtype}'
19555a74a43SLisandro Dalcin    doc = f'"""{prop.__doc__}"""'
19655a74a43SLisandro Dalcin    doc = textwrap.indent(doc, Lines.INDENT)
197*6f336411SStefano Zampini    body = Lines.INDENT + '...'
198*6f336411SStefano Zampini    return f'@property\ndef {sig}:\n{doc}\n{body}\n'
19955a74a43SLisandro Dalcin
20055a74a43SLisandro Dalcin
20155a74a43SLisandro Dalcindef visit_constructor(cls, name='__init__', args=None):
202*6f336411SStefano Zampini    init = name == '__init__'
20355a74a43SLisandro Dalcin    argname = cls.__mro__[-2].__name__.lower()
20455a74a43SLisandro Dalcin    argtype = cls.__name__
205*6f336411SStefano Zampini    initarg = args or f'{argname}: Optional[{argtype}] = None'
20655a74a43SLisandro Dalcin    selfarg = 'self' if init else 'cls'
20755a74a43SLisandro Dalcin    rettype = 'None' if init else argtype
208*6f336411SStefano Zampini    arglist = f'{selfarg}, {initarg}'
209*6f336411SStefano Zampini    sig = f'{name}({arglist}) -> {rettype}'
21055a74a43SLisandro Dalcin    ret = '...' if init else 'return super().__new__(cls)'
21155a74a43SLisandro Dalcin    body = Lines.INDENT + ret
212*6f336411SStefano Zampini    return f'def {sig}:\n{body}'
21355a74a43SLisandro Dalcin
21455a74a43SLisandro Dalcin
21555a74a43SLisandro Dalcindef visit_class(cls, outer=None, done=None):
21655a74a43SLisandro Dalcin    skip = {
21755a74a43SLisandro Dalcin        '__doc__',
21855a74a43SLisandro Dalcin        '__dict__',
21955a74a43SLisandro Dalcin        '__module__',
22055a74a43SLisandro Dalcin        '__weakref__',
22155a74a43SLisandro Dalcin        '__pyx_vtable__',
22255a74a43SLisandro Dalcin        '__lt__',
22355a74a43SLisandro Dalcin        '__le__',
22455a74a43SLisandro Dalcin        '__ge__',
22555a74a43SLisandro Dalcin        '__gt__',
226af271053SLisandro Dalcin        '__enum2str',  # FIXME refactor implementation
22755a74a43SLisandro Dalcin        '_traceback_',  # FIXME maybe refactor?
22855a74a43SLisandro Dalcin    }
22955a74a43SLisandro Dalcin    special = {
230*6f336411SStefano Zampini        '__len__': '__len__(self) -> int',
231*6f336411SStefano Zampini        '__bool__': '__bool__(self) -> bool',
232*6f336411SStefano Zampini        '__hash__': '__hash__(self) -> int',
233*6f336411SStefano Zampini        '__int__': '__int__(self) -> int',
234*6f336411SStefano Zampini        '__index__': '__int__(self) -> int',
235*6f336411SStefano Zampini        '__str__': '__str__(self) -> str',
236*6f336411SStefano Zampini        '__repr__': '__repr__(self) -> str',
237*6f336411SStefano Zampini        '__eq__': '__eq__(self, other: object) -> bool',
238*6f336411SStefano Zampini        '__ne__': '__ne__(self, other: object) -> bool',
23955a74a43SLisandro Dalcin    }
24055a74a43SLisandro Dalcin
24155a74a43SLisandro Dalcin    qualname = cls.__name__
24255a74a43SLisandro Dalcin    cls_name = cls.__name__
24355a74a43SLisandro Dalcin    if outer is not None and cls_name.startswith(outer):
24455a74a43SLisandro Dalcin        cls_name = cls_name[len(outer) :]
245*6f336411SStefano Zampini        qualname = f'{outer}.{cls_name}'
24655a74a43SLisandro Dalcin
24755a74a43SLisandro Dalcin    override = OVERRIDE.get(qualname, {})
24855a74a43SLisandro Dalcin    done = set() if done is None else done
24955a74a43SLisandro Dalcin    lines = Lines()
25055a74a43SLisandro Dalcin
25155a74a43SLisandro Dalcin    base = cls.__base__
25255a74a43SLisandro Dalcin    if base is object:
253*6f336411SStefano Zampini        lines.add = f'class {cls_name}:'
25455a74a43SLisandro Dalcin    else:
255*6f336411SStefano Zampini        lines.add = f'class {cls_name}({base.__name__}):'
25655a74a43SLisandro Dalcin    lines.level += 1
25755a74a43SLisandro Dalcin
25855a74a43SLisandro Dalcin    lines.add = docstring(cls)
25955a74a43SLisandro Dalcin
26055a74a43SLisandro Dalcin    for name in ('__new__', '__init__', '__hash__'):
26155a74a43SLisandro Dalcin        if name in cls.__dict__:
26255a74a43SLisandro Dalcin            done.add(name)
26355a74a43SLisandro Dalcin
26455a74a43SLisandro Dalcin    dct = cls.__dict__
26555a74a43SLisandro Dalcin    keys = list(dct.keys())
26655a74a43SLisandro Dalcin
26755a74a43SLisandro Dalcin    def dunder(name):
26855a74a43SLisandro Dalcin        return name.startswith('__') and name.endswith('__')
26955a74a43SLisandro Dalcin
27055a74a43SLisandro Dalcin    def members(seq):
27155a74a43SLisandro Dalcin        for name in seq:
27255a74a43SLisandro Dalcin            if name in skip:
27355a74a43SLisandro Dalcin                continue
27455a74a43SLisandro Dalcin            if name in done:
27555a74a43SLisandro Dalcin                continue
27655a74a43SLisandro Dalcin            if dunder(name):
27755a74a43SLisandro Dalcin                if name not in special and name not in override:
27855a74a43SLisandro Dalcin                    done.add(name)
27955a74a43SLisandro Dalcin                    continue
28055a74a43SLisandro Dalcin            yield name
28155a74a43SLisandro Dalcin
28255a74a43SLisandro Dalcin    for name in members(keys):
28355a74a43SLisandro Dalcin        attr = getattr(cls, name)
28455a74a43SLisandro Dalcin        if is_class(attr):
28555a74a43SLisandro Dalcin            done.add(name)
28655a74a43SLisandro Dalcin            lines.add = visit_class(attr, outer=cls_name)
28755a74a43SLisandro Dalcin            continue
28855a74a43SLisandro Dalcin
28955a74a43SLisandro Dalcin    for name in members(keys):
29055a74a43SLisandro Dalcin        if name in override:
29155a74a43SLisandro Dalcin            done.add(name)
29255a74a43SLisandro Dalcin            lines.add = override[name]
29355a74a43SLisandro Dalcin            continue
29455a74a43SLisandro Dalcin
29555a74a43SLisandro Dalcin        if name in special:
29655a74a43SLisandro Dalcin            done.add(name)
29755a74a43SLisandro Dalcin            sig = special[name]
298*6f336411SStefano Zampini            lines.add = f'def {sig}: ...'
29955a74a43SLisandro Dalcin            continue
30055a74a43SLisandro Dalcin
30155a74a43SLisandro Dalcin        attr = getattr(cls, name)
30255a74a43SLisandro Dalcin
30355a74a43SLisandro Dalcin        if is_method(attr):
30455a74a43SLisandro Dalcin            done.add(name)
30555a74a43SLisandro Dalcin            if name == attr.__name__:
30655a74a43SLisandro Dalcin                obj = dct[name]
30755a74a43SLisandro Dalcin                if is_classmethod(obj):
308*6f336411SStefano Zampini                    lines.add = '@classmethod'
30955a74a43SLisandro Dalcin                elif is_staticmethod(obj):
310*6f336411SStefano Zampini                    lines.add = '@staticmethod'
31155a74a43SLisandro Dalcin                lines.add = visit_method(attr)
31255a74a43SLisandro Dalcin            continue
31355a74a43SLisandro Dalcin
31455a74a43SLisandro Dalcin        if is_datadescr(attr):
31555a74a43SLisandro Dalcin            done.add(name)
31655a74a43SLisandro Dalcin            lines.add = visit_datadescr(attr)
31755a74a43SLisandro Dalcin            continue
31855a74a43SLisandro Dalcin
31955a74a43SLisandro Dalcin        if is_property(attr):
32055a74a43SLisandro Dalcin            done.add(name)
32155a74a43SLisandro Dalcin            lines.add = visit_property(attr, name)
32255a74a43SLisandro Dalcin            continue
32355a74a43SLisandro Dalcin
32455a74a43SLisandro Dalcin        if is_constant(attr):
32555a74a43SLisandro Dalcin            done.add(name)
32655a74a43SLisandro Dalcin            lines.add = visit_data((name, attr))
32755a74a43SLisandro Dalcin            continue
32855a74a43SLisandro Dalcin
329*6f336411SStefano Zampini    leftovers = [name for name in keys if name not in done and name not in skip]
33055a74a43SLisandro Dalcin    if leftovers:
331*6f336411SStefano Zampini        raise RuntimeError(f'leftovers: {leftovers}')
33255a74a43SLisandro Dalcin
33355a74a43SLisandro Dalcin    lines.level -= 1
33455a74a43SLisandro Dalcin    return lines
33555a74a43SLisandro Dalcin
33655a74a43SLisandro Dalcin
33755a74a43SLisandro Dalcindef visit_module(module, done=None):
33855a74a43SLisandro Dalcin    skip = {
33955a74a43SLisandro Dalcin        '__doc__',
34055a74a43SLisandro Dalcin        '__name__',
34155a74a43SLisandro Dalcin        '__loader__',
34255a74a43SLisandro Dalcin        '__spec__',
34355a74a43SLisandro Dalcin        '__file__',
34455a74a43SLisandro Dalcin        '__package__',
34555a74a43SLisandro Dalcin        '__builtins__',
34655a74a43SLisandro Dalcin        '__pyx_capi__',
34755a74a43SLisandro Dalcin        '__pyx_unpickle_Enum',  # FIXME review
34855a74a43SLisandro Dalcin    }
34955a74a43SLisandro Dalcin
35055a74a43SLisandro Dalcin    done = set() if done is None else done
35155a74a43SLisandro Dalcin    lines = Lines()
35255a74a43SLisandro Dalcin
35355a74a43SLisandro Dalcin    keys = list(module.__dict__.keys())
354*6f336411SStefano Zampini    keys.sort(key=lambda name: name.startswith('_'))
35555a74a43SLisandro Dalcin
35655a74a43SLisandro Dalcin    constants = [
357*6f336411SStefano Zampini        (name, getattr(module, name))
358*6f336411SStefano Zampini        for name in keys
359*6f336411SStefano Zampini        if all(
360*6f336411SStefano Zampini            (
36155a74a43SLisandro Dalcin                name not in done and name not in skip,
36255a74a43SLisandro Dalcin                is_constant(getattr(module, name)),
363*6f336411SStefano Zampini            )
364*6f336411SStefano Zampini        )
36555a74a43SLisandro Dalcin    ]
36655a74a43SLisandro Dalcin    for _, value in constants:
36755a74a43SLisandro Dalcin        cls = type(value)
36855a74a43SLisandro Dalcin        name = cls.__name__
36955a74a43SLisandro Dalcin        if name in done or name in skip:
37055a74a43SLisandro Dalcin            continue
37155a74a43SLisandro Dalcin        if cls.__module__ == module.__name__:
37255a74a43SLisandro Dalcin            done.add(name)
37355a74a43SLisandro Dalcin            lines.add = visit_class(cls)
374*6f336411SStefano Zampini            lines.add = ''
37555a74a43SLisandro Dalcin    for attr in constants:
37655a74a43SLisandro Dalcin        name, value = attr
37755a74a43SLisandro Dalcin        done.add(name)
37855a74a43SLisandro Dalcin        if name in OVERRIDE:
37955a74a43SLisandro Dalcin            lines.add = OVERRIDE[name]
38055a74a43SLisandro Dalcin        else:
38155a74a43SLisandro Dalcin            lines.add = visit_data((name, value))
38255a74a43SLisandro Dalcin    if constants:
383*6f336411SStefano Zampini        lines.add = ''
38455a74a43SLisandro Dalcin
38555a74a43SLisandro Dalcin    for name in keys:
38655a74a43SLisandro Dalcin        if name in done or name in skip:
38755a74a43SLisandro Dalcin            continue
38855a74a43SLisandro Dalcin        value = getattr(module, name)
38955a74a43SLisandro Dalcin
39055a74a43SLisandro Dalcin        if is_class(value):
39155a74a43SLisandro Dalcin            done.add(name)
39255a74a43SLisandro Dalcin            if value.__name__ != name:
39355a74a43SLisandro Dalcin                continue
39455a74a43SLisandro Dalcin            if value.__module__ != module.__name__:
39555a74a43SLisandro Dalcin                continue
39655a74a43SLisandro Dalcin            lines.add = visit_class(value)
397*6f336411SStefano Zampini            lines.add = ''
39855a74a43SLisandro Dalcin            instances = [
399*6f336411SStefano Zampini                (k, getattr(module, k))
400*6f336411SStefano Zampini                for k in keys
401*6f336411SStefano Zampini                if all(
402*6f336411SStefano Zampini                    (
40355a74a43SLisandro Dalcin                        k not in done and k not in skip,
40455a74a43SLisandro Dalcin                        type(getattr(module, k)) is value,
405*6f336411SStefano Zampini                    )
406*6f336411SStefano Zampini                )
40755a74a43SLisandro Dalcin            ]
40855a74a43SLisandro Dalcin            for attrname, attrvalue in instances:
40955a74a43SLisandro Dalcin                done.add(attrname)
41055a74a43SLisandro Dalcin                lines.add = visit_data((attrname, attrvalue))
41155a74a43SLisandro Dalcin            if instances:
412*6f336411SStefano Zampini                lines.add = ''
41355a74a43SLisandro Dalcin            continue
41455a74a43SLisandro Dalcin
41555a74a43SLisandro Dalcin        if is_function(value):
41655a74a43SLisandro Dalcin            done.add(name)
41755a74a43SLisandro Dalcin            if name == value.__name__:
41855a74a43SLisandro Dalcin                lines.add = visit_function(value)
41955a74a43SLisandro Dalcin            else:
420*6f336411SStefano Zampini                lines.add = f'{name} = {value.__name__}'
42155a74a43SLisandro Dalcin            continue
42255a74a43SLisandro Dalcin
423*6f336411SStefano Zampini    lines.add = ''
42455a74a43SLisandro Dalcin    for name in keys:
42555a74a43SLisandro Dalcin        if name in done or name in skip:
42655a74a43SLisandro Dalcin            continue
42755a74a43SLisandro Dalcin        value = getattr(module, name)
42855a74a43SLisandro Dalcin        done.add(name)
42955a74a43SLisandro Dalcin        if name in OVERRIDE:
43055a74a43SLisandro Dalcin            lines.add = OVERRIDE[name]
43155a74a43SLisandro Dalcin        else:
43255a74a43SLisandro Dalcin            lines.add = visit_data((name, value))
43355a74a43SLisandro Dalcin
434*6f336411SStefano Zampini    leftovers = [name for name in keys if name not in done and name not in skip]
43555a74a43SLisandro Dalcin    if leftovers:
436*6f336411SStefano Zampini        raise RuntimeError(f'leftovers: {leftovers}')
43755a74a43SLisandro Dalcin    return lines
43855a74a43SLisandro Dalcin
43955a74a43SLisandro Dalcin
44055a74a43SLisandro DalcinIMPORTS = """
44155a74a43SLisandro Dalcinfrom __future__ import annotations
44255a74a43SLisandro Dalcinimport sys
44355a74a43SLisandro Dalcinfrom typing import (
44455a74a43SLisandro Dalcin    Any,
44555a74a43SLisandro Dalcin    Union,
44655a74a43SLisandro Dalcin    Literal,
44755a74a43SLisandro Dalcin    Optional,
44855a74a43SLisandro Dalcin    NoReturn,
44955a74a43SLisandro Dalcin    Final,
45055a74a43SLisandro Dalcin)
45155a74a43SLisandro Dalcinfrom typing import (
45255a74a43SLisandro Dalcin    Callable,
45355a74a43SLisandro Dalcin    Hashable,
45455a74a43SLisandro Dalcin    Iterable,
45555a74a43SLisandro Dalcin    Iterator,
45655a74a43SLisandro Dalcin    Sequence,
45755a74a43SLisandro Dalcin    Mapping,
45855a74a43SLisandro Dalcin)
45955a74a43SLisandro Dalcinif sys.version_info >= (3, 11):
46055a74a43SLisandro Dalcin    from typing import Self
46155a74a43SLisandro Dalcinelse:
46255a74a43SLisandro Dalcin    from typing_extensions import Self
46355a74a43SLisandro Dalcin
46455a74a43SLisandro Dalcinimport numpy
46555a74a43SLisandro Dalcinfrom numpy import dtype, ndarray
46655a74a43SLisandro Dalcinfrom mpi4py.MPI import (
46755a74a43SLisandro Dalcin    Intracomm,
46855a74a43SLisandro Dalcin    Datatype,
46955a74a43SLisandro Dalcin    Op,
47055a74a43SLisandro Dalcin)
47155a74a43SLisandro Dalcin
47255a74a43SLisandro Dalcinclass _dtype:
47355a74a43SLisandro Dalcin    def __init__(self, name):
47455a74a43SLisandro Dalcin        self.name = name
47555a74a43SLisandro Dalcin    def __repr__(self):
47655a74a43SLisandro Dalcin        return self.name
47755a74a43SLisandro Dalcin
47855a74a43SLisandro DalcinIntType: dtype = _dtype('IntType')
47955a74a43SLisandro DalcinRealType: dtype =  _dtype('RealType')
48055a74a43SLisandro DalcinComplexType: dtype = _dtype('ComplexType')
48155a74a43SLisandro DalcinScalarType: dtype = _dtype('ScalarType')
48255a74a43SLisandro Dalcin"""
48355a74a43SLisandro Dalcin
48455a74a43SLisandro DalcinHELPERS = """
48555a74a43SLisandro Dalcinclass _Int(int): pass
48655a74a43SLisandro Dalcinclass _Str(str): pass
48755a74a43SLisandro Dalcinclass _Float(float): pass
48855a74a43SLisandro Dalcinclass _Dict(dict): pass
48955a74a43SLisandro Dalcin
49055a74a43SLisandro Dalcindef _repr(obj):
49155a74a43SLisandro Dalcin    try:
49255a74a43SLisandro Dalcin        return obj._name
49355a74a43SLisandro Dalcin    except AttributeError:
49455a74a43SLisandro Dalcin        return super(obj).__repr__()
49555a74a43SLisandro Dalcin
49655a74a43SLisandro Dalcindef _def(cls, name):
49755a74a43SLisandro Dalcin    if cls is int:
49855a74a43SLisandro Dalcin       cls = _Int
49955a74a43SLisandro Dalcin    if cls is str:
50055a74a43SLisandro Dalcin       cls = _Str
50155a74a43SLisandro Dalcin    if cls is float:
50255a74a43SLisandro Dalcin       cls = _Float
50355a74a43SLisandro Dalcin    if cls is dict:
50455a74a43SLisandro Dalcin       cls = _Dict
50555a74a43SLisandro Dalcin
50655a74a43SLisandro Dalcin    obj = cls()
50755a74a43SLisandro Dalcin    obj._name = name
50855a74a43SLisandro Dalcin    if '__repr__' not in cls.__dict__:
50955a74a43SLisandro Dalcin        cls.__repr__ = _repr
51055a74a43SLisandro Dalcin    return obj
51155a74a43SLisandro Dalcin"""
51255a74a43SLisandro Dalcin
513*6f336411SStefano ZampiniOVERRIDE = {}
51455a74a43SLisandro Dalcin
51555a74a43SLisandro DalcinTYPING = """
51655a74a43SLisandro Dalcinfrom .typing import *
51755a74a43SLisandro Dalcin"""
51855a74a43SLisandro Dalcin
51955a74a43SLisandro Dalcin
52055a74a43SLisandro Dalcindef visit_petsc4py_PETSc(done=None):
52155a74a43SLisandro Dalcin    from petsc4py import PETSc
522*6f336411SStefano Zampini
52355a74a43SLisandro Dalcin    lines = Lines()
52455a74a43SLisandro Dalcin    lines.add = f'"""{PETSc.__doc__}"""'
52555a74a43SLisandro Dalcin    lines.add = IMPORTS
526*6f336411SStefano Zampini    lines.add = ''
52755a74a43SLisandro Dalcin    lines.add = HELPERS
528*6f336411SStefano Zampini    lines.add = ''
52955a74a43SLisandro Dalcin    lines.add = visit_module(PETSc)
530*6f336411SStefano Zampini    lines.add = ''
53155a74a43SLisandro Dalcin    lines.add = TYPING
53255a74a43SLisandro Dalcin    return lines
53355a74a43SLisandro Dalcin
53455a74a43SLisandro Dalcin
53555a74a43SLisandro Dalcindef generate(filename):
53655a74a43SLisandro Dalcin    dirname = os.path.dirname(filename)
53755a74a43SLisandro Dalcin    os.makedirs(dirname, exist_ok=True)
53855a74a43SLisandro Dalcin    with open(filename, 'w') as f:
53955a74a43SLisandro Dalcin        for line in visit_petsc4py_PETSc():
54055a74a43SLisandro Dalcin            print(line, file=f)
54155a74a43SLisandro Dalcin
54255a74a43SLisandro Dalcin
54355a74a43SLisandro Dalcindef load_module(filename, name=None):
54455a74a43SLisandro Dalcin    if name is None:
545*6f336411SStefano Zampini        name, _ = os.path.splitext(os.path.basename(filename))
54655a74a43SLisandro Dalcin    module = type(sys)(name)
54755a74a43SLisandro Dalcin    module.__file__ = filename
54855a74a43SLisandro Dalcin    module.__package__ = name.rsplit('.', 1)[0]
54955a74a43SLisandro Dalcin    old = replace_module(module)
55055a74a43SLisandro Dalcin    with open(filename) as f:
55155a74a43SLisandro Dalcin        exec(f.read(), module.__dict__)  # noqa: S102
55255a74a43SLisandro Dalcin    restore_module(old)
55355a74a43SLisandro Dalcin    return module
55455a74a43SLisandro Dalcin
55555a74a43SLisandro Dalcin
55655a74a43SLisandro Dalcin_sys_modules = {}
55755a74a43SLisandro Dalcin
55855a74a43SLisandro Dalcin
55955a74a43SLisandro Dalcindef replace_module(module):
56055a74a43SLisandro Dalcin    name = module.__name__
561*6f336411SStefano Zampini    if name in _sys_modules:
562*6f336411SStefano Zampini        raise RuntimeError(f'{name} in modules')
56355a74a43SLisandro Dalcin    _sys_modules[name] = sys.modules[name]
56455a74a43SLisandro Dalcin    sys.modules[name] = module
56555a74a43SLisandro Dalcin    return _sys_modules[name]
56655a74a43SLisandro Dalcin
56755a74a43SLisandro Dalcin
56855a74a43SLisandro Dalcindef restore_module(module):
56955a74a43SLisandro Dalcin    name = module.__name__
570*6f336411SStefano Zampini    if name not in _sys_modules:
571*6f336411SStefano Zampini        raise RuntimeError(f'{name} not in modules')
57255a74a43SLisandro Dalcin    sys.modules[name] = _sys_modules[name]
57355a74a43SLisandro Dalcin    del _sys_modules[name]
57455a74a43SLisandro Dalcin
57555a74a43SLisandro Dalcin
57655a74a43SLisandro Dalcindef annotate(dest, source):
57755a74a43SLisandro Dalcin    try:
57855a74a43SLisandro Dalcin        dest.__annotations__ = source.__annotations__
57955a74a43SLisandro Dalcin    except AttributeError:
58055a74a43SLisandro Dalcin        pass
58155a74a43SLisandro Dalcin    if isinstance(dest, type):
58255a74a43SLisandro Dalcin        for name in dest.__dict__.keys():
58355a74a43SLisandro Dalcin            if hasattr(source, name):
58455a74a43SLisandro Dalcin                obj = getattr(dest, name)
58555a74a43SLisandro Dalcin                annotate(obj, getattr(source, name))
58655a74a43SLisandro Dalcin    if isinstance(dest, type(sys)):
58755a74a43SLisandro Dalcin        for name in dir(dest):
58855a74a43SLisandro Dalcin            if hasattr(source, name):
58955a74a43SLisandro Dalcin                obj = getattr(dest, name)
59055a74a43SLisandro Dalcin                mod = getattr(obj, '__module__', None)
59155a74a43SLisandro Dalcin                if dest.__name__ == mod:
59255a74a43SLisandro Dalcin                    annotate(obj, getattr(source, name))
59355a74a43SLisandro Dalcin        for name in dir(source):
59455a74a43SLisandro Dalcin            if not hasattr(dest, name):
59555a74a43SLisandro Dalcin                setattr(dest, name, getattr(source, name))
59655a74a43SLisandro Dalcin
59755a74a43SLisandro Dalcin
59855a74a43SLisandro DalcinOUTDIR = 'reference'
59955a74a43SLisandro Dalcin
60055a74a43SLisandro Dalcinif __name__ == '__main__':
60155a74a43SLisandro Dalcin    generate(os.path.join(OUTDIR, 'petsc4py.PETSc.py'))
602