import inspect from typing import Callable, Optional from nicegui import binding, ui from nicegui.elements.markdown import remove_indentation from ..style import create_anchor_name, subheading def generate_class_doc(class_obj: type, part_title: str) -> None: """Generate documentation for a class including all its methods and properties.""" doc = class_obj.__doc__ or class_obj.__init__.__doc__ if doc and ':param' in doc: subheading('Initializer', anchor_name=create_anchor_name(part_title.replace('Reference', 'Initializer'))) description = remove_indentation(doc.split('\n', 1)[-1]) lines = [line.replace(':param ', ':') for line in description.splitlines() if ':param' in line] ui.restructured_text('\n'.join(lines)).classes('bold-links arrow-links rst-param-tables') mro = [base for base in class_obj.__mro__ if base.__module__.startswith('nicegui.')] ancestors = mro[1:] attributes = {} for base in reversed(mro): for name in dir(base): if not name.startswith('_') and _is_method_or_property(base, name): attributes[name] = getattr(base, name, None) properties = {name: attribute for name, attribute in attributes.items() if not callable(attribute)} methods = {name: attribute for name, attribute in attributes.items() if callable(attribute)} if properties: subheading('Properties', anchor_name=create_anchor_name(part_title.replace('Reference', 'Properties'))) with ui.column().classes('gap-2'): for name, property_ in sorted(properties.items()): ui.markdown(f'**`{name}`**`{_generate_property_signature_description(property_)}`') if property_.__doc__: _render_docstring(property_.__doc__).classes('ml-8') if methods: subheading('Methods', anchor_name=create_anchor_name(part_title.replace('Reference', 'Methods'))) with ui.column().classes('gap-2'): for name, method in sorted(methods.items()): decorator = '' if isinstance(class_obj.__dict__.get(name), staticmethod): decorator += '`@staticmethod`
' if isinstance(class_obj.__dict__.get(name), classmethod): decorator += '`@classmethod`
' ui.markdown(f'{decorator}**`{name}`**`{_generate_method_signature_description(method)}`') if method.__doc__: _render_docstring(method.__doc__).classes('ml-8') if ancestors: subheading('Inheritance', anchor_name=create_anchor_name(part_title.replace('Reference', 'Inheritance'))) ui.markdown('\n'.join(f'- `{ancestor.__name__}`' for ancestor in ancestors)) def _is_method_or_property(cls: type, attribute_name: str) -> bool: attribute = cls.__dict__.get(attribute_name, None) return ( inspect.isfunction(attribute) or inspect.ismethod(attribute) or isinstance(attribute, ( staticmethod, classmethod, property, binding.BindableProperty, )) ) def _generate_property_signature_description(property_: Optional[property]) -> str: description = '' if property_ is None: return ': BindableProperty' if property_.fget: return_annotation = inspect.signature(property_.fget).return_annotation if return_annotation != inspect.Parameter.empty: return_type = inspect.formatannotation(return_annotation) description += f': {return_type}' if property_.fset: description += ' (settable)' if property_.fdel: description += ' (deletable)' return description def _generate_method_signature_description(method: Callable) -> str: param_strings = [] for param in inspect.signature(method).parameters.values(): param_string = param.name if param_string == 'self': continue if param.annotation != inspect.Parameter.empty: param_type = inspect.formatannotation(param.annotation) param_string += f''': {param_type.strip("'")}''' if param.default != inspect.Parameter.empty: param_string += ' = [...]' if callable(param.default) else f' = {param.default!r}' if param.kind == inspect.Parameter.VAR_POSITIONAL: param_string = f'*{param_string}' param_strings.append(param_string) method_signature = ', '.join(param_strings) description = f'({method_signature})' return_annotation = inspect.signature(method).return_annotation if return_annotation != inspect.Parameter.empty: return_type = inspect.formatannotation(return_annotation) description += f''' -> {return_type.strip("'").replace("typing_extensions.", "").replace("typing.", "")}''' return description def _render_docstring(doc: str) -> ui.restructured_text: doc = _remove_indentation_from_docstring(doc) return ui.restructured_text(doc).classes('bold-links arrow-links rst-param-tables') def _remove_indentation_from_docstring(text: str) -> str: lines = text.splitlines() if not lines: return '' if len(lines) == 1: return lines[0] indentation = min(len(line) - len(line.lstrip()) for line in lines[1:] if line.strip()) return lines[0] + '\n' + '\n'.join(line[indentation:] for line in lines[1:])