def extract_enum_doc(state: State, path: List[str], enum_): out = Empty() out.name = path[-1] out.values = [] out.has_details = False out.has_value_details = False # The happy case if issubclass(enum_, enum.Enum): # Enum doc is by default set to a generic value. That's useless as well. if enum_.__doc__ == 'An enumeration.': out.summary = '' else: # TODO: external summary for enums out.summary = extract_summary(state, {}, [], enum_.__doc__) out.base = extract_type(enum_.__base__) for i in enum_: value = Empty() value.name = i.name value.value = html.escape(repr(i.value)) # Value doc gets by default inherited from the enum, that's useless if i.__doc__ == enum_.__doc__: value.summary = '' else: # TODO: external summary for enum values value.summary = extract_summary(state, {}, [], i.__doc__) if value.summary: out.has_details = True out.has_value_details = True out.values += [value] # Pybind11 enums are ... different elif state.config['PYBIND11_COMPATIBILITY']: assert hasattr(enum_, '__members__') # TODO: external summary for enums out.summary = extract_summary(state, {}, [], enum_.__doc__) out.base = None for name, v in enum_.__members__.items(): value = Empty() value. name = name value.value = int(v) # TODO: once https://github.com/pybind/pybind11/pull/1160 is # released, extract from class docs (until then the class # docstring is duplicated here, which is useless) value.summary = '' out.values += [value] return out
def extract_data_doc(state: State, parent, path: List[str], data): assert not inspect.ismodule(data) and not inspect.isclass(data) and not inspect.isroutine(data) and not inspect.isframe(data) and not inspect.istraceback(data) and not inspect.iscode(data) out = Empty() out.name = path[-1] # Welp. https://stackoverflow.com/questions/8820276/docstring-for-variable out.summary = '' out.has_details = False if hasattr(parent, '__annotations__') and out.name in parent.__annotations__: out.type = extract_annotation(state, parent.__annotations__[out.name]) else: out.type = None # The autogenerated <foo.bar at 0xbadbeef> is useless, so provide the value # only if __repr__ is implemented for given type if '__repr__' in type(data).__dict__: out.value = html.escape(repr(data)) else: out.value = None # External data summary, if provided path_str = '.'.join(path) if path_str in state.data_docs: # TODO: use also the contents out.summary = render_inline_rst(state, state.data_docs[path_str]['summary']) del state.data_docs[path_str] return out
def extract_property_doc(state: State, path: List[str], property): assert inspect.isdatadescriptor(property) out = Empty() out.name = path[-1] # TODO: external summary for properties out.summary = extract_summary(state, {}, [], property.__doc__) out.is_settable = property.fset is not None out.is_deletable = property.fdel is not None out.has_details = False try: signature = inspect.signature(property.fget) out.type = extract_annotation(state, signature.return_annotation) except ValueError: # pybind11 properties have the type in the docstring if state.config['PYBIND11_COMPATIBILITY']: out.type = parse_pybind_signature(state, property.fget.__doc__)[3] else: out.type = None return out
def extract_function_doc(state: State, parent, path: List[str], function) -> List[Any]: assert inspect.isfunction(function) or inspect.ismethod(function) or inspect.isroutine(function) # Extract the signature from the docstring for pybind11, since it can't # expose it to the metadata: https://github.com/pybind/pybind11/issues/990 # What's not solvable with metadata, however, are function overloads --- # one function in Python may equal more than one function on the C++ side. # To make the docs usable, list all overloads separately. if state.config['PYBIND11_COMPATIBILITY'] and function.__doc__.startswith(path[-1]): funcs = parse_pybind_docstring(state, path[-1], function.__doc__) overloads = [] for name, summary, args, type in funcs: out = Empty() out.name = path[-1] out.params = [] out.has_complex_params = False out.has_details = False # TODO: external summary for functions out.summary = summary # Don't show None return type for void functions out.type = None if type == 'None' else type # There's no other way to check staticmethods than to check for # self being the name of first parameter :( No support for # classmethods, as C++11 doesn't have that out.is_classmethod = False if inspect.isclass(parent) and args and args[0][0] == 'self': out.is_staticmethod = False else: out.is_staticmethod = True # Guesstimate whether the arguments are positional-only or # position-or-keyword. It's either all or none. This is a brown # magic, sorry. # For instance methods positional-only argument names are either # self (for the first argument) or arg(I-1) (for second # argument and further). Also, the `self` argument is # positional-or-keyword only if there are positional-or-keyword # arguments afgter it, otherwise it's positional-only. if inspect.isclass(parent) and not out.is_staticmethod: assert args and args[0][0] == 'self' positional_only = True for i, arg in enumerate(args[1:]): name, type, default = arg if name != 'arg{}'.format(i): positional_only = False break # For static methods or free functions positional-only arguments # are argI. else: positional_only = True for i, arg in enumerate(args): name, type, default = arg if name != 'arg{}'.format(i): positional_only = False break for i, arg in enumerate(args): name, type, default = arg param = Empty() param.name = name # Don't include redundant type for the self argument if name == 'self': param.type = None else: param.type = type param.default = html.escape(default or '') if type or default: out.has_complex_params = True # *args / **kwargs can still appear in the parsed signatures if # the function accepts py::args / py::kwargs directly if name == '*args': param.name = 'args' param.kind = 'VAR_POSITIONAL' elif name == '**kwargs': param.name = 'kwargs' param.kind = 'VAR_KEYWORD' else: param.kind = 'POSITIONAL_ONLY' if positional_only else 'POSITIONAL_OR_KEYWORD' out.params += [param] overloads += [out] return overloads # Sane introspection path for non-pybind11 code else: out = Empty() out.name = path[-1] out.params = [] out.has_complex_params = False out.has_details = False # TODO: external summary for functions out.summary = extract_summary(state, {}, [], function.__doc__) # Decide if classmethod or staticmethod in case this is a method if inspect.isclass(parent): out.is_classmethod = inspect.ismethod(function) out.is_staticmethod = out.name in parent.__dict__ and isinstance(parent.__dict__[out.name], staticmethod) try: signature = inspect.signature(function) out.type = extract_annotation(state, signature.return_annotation) for i in signature.parameters.values(): param = Empty() param.name = i.name param.type = extract_annotation(state, i.annotation) if param.type: out.has_complex_params = True if i.default is inspect.Signature.empty: param.default = None else: param.default = repr(i.default) out.has_complex_params = True param.kind = str(i.kind) out.params += [param] # In CPython, some builtin functions (such as math.log) do not provide # metadata about their arguments. Source: # https://docs.python.org/3/library/inspect.html#inspect.signature except ValueError: param = Empty() param.name = '...' param.name_type = param.name out.params = [param] out.type = None return [out]