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