Skip to content
Merged
76 changes: 53 additions & 23 deletions website/documentation/reference.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import inspect
from dataclasses import dataclass
from typing import Callable, Optional

from nicegui import binding, ui
from nicegui.dataclasses import KWONLY_SLOTS
from nicegui.elements.markdown import remove_indentation

from ..style import create_anchor_name, subheading
from .custom_restructured_text import CustomRestructuredText as custom_restructured_text


@dataclass(**KWONLY_SLOTS)
class Attribute:
name: str
obj: object | None
base: type


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__
Expand All @@ -19,39 +28,60 @@ def generate_class_doc(class_obj: type, part_title: str) -> None:

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)}
attributes = sorted([
Attribute(name=name, obj=getattr(base, name, None), base=base)
for base in reversed(mro)
for name in dir(base)
if not name.startswith('_') and _is_method_or_property(base, name)
], key=lambda x: x.name)
properties = [attribute for attribute in attributes if not callable(attribute.obj)]
methods = [attribute for attribute in attributes if callable(attribute.obj)]

if properties:
subheading('Properties', anchor_name=create_anchor_name(part_title.replace('Reference', 'Properties')))
with ui.column().classes('gap-2 w-full overflow-x-auto'):
for name, property_ in sorted(properties.items()):
ui.markdown(f'**`{name}`**`{_generate_property_signature_description(property_)}`')
if property_ is not None and property_.__doc__:
_render_docstring(property_.__doc__).classes('ml-8')
_render_section(class_obj, properties, method_section=False)

if methods:
subheading('Methods', anchor_name=create_anchor_name(part_title.replace('Reference', 'Methods')))
with ui.column().classes('gap-2 w-full overflow-x-auto'):
for name, method in sorted(methods.items()):
decorator = ''
if isinstance(class_obj.__dict__.get(name), staticmethod):
decorator += '`@staticmethod`<br />'
if isinstance(class_obj.__dict__.get(name), classmethod):
decorator += '`@classmethod`<br />'
ui.markdown(f'{decorator}**`{name}`**`{_generate_method_signature_description(method)}`') \
.classes('w-full overflow-x-auto')
if method.__doc__:
_render_docstring(method.__doc__).classes('ml-8')
_render_section(class_obj, methods, method_section=True)

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 _render_section(class_obj: type, attributes: list[Attribute], *, method_section: bool) -> None:
native_attributes = [attribute for attribute in attributes if attribute.base is class_obj]
if native_attributes:
with ui.column().classes('gap-2 w-full overflow-x-auto'):
for native_attribute in native_attributes:
_render_attribute(native_attribute, method_section=method_section)

inherited_attributes = [attribute for attribute in attributes if attribute.base is not class_obj]
if inherited_attributes:
with ui.expansion(f'Inherited {"methods" if method_section else "properties"}', icon='account_tree', value=True) \
.classes('w-full border border-gray-200 dark:border-gray-800 rounded-md'):
for attribute in inherited_attributes:
_render_attribute(attribute, method_section=method_section)


def _render_attribute(item: Attribute, *, method_section: bool) -> None:
if method_section:
decorator = ''
owner_attr = item.base.__dict__.get(item.name)
if isinstance(owner_attr, staticmethod):
decorator += '`@staticmethod`<br />'
if isinstance(owner_attr, classmethod):
decorator += '`@classmethod`<br />'
ui.markdown(f'{decorator}**`{item.name}`**`{_generate_method_signature_description(item.obj)}`') \
.classes('w-full overflow-x-auto')
else:
ui.markdown(f'**`{item.name}`**`{_generate_property_signature_description(item.obj)}`')
docstring = getattr(item.obj, '__doc__', None)
if item.obj is not None and docstring:
_render_docstring(docstring).classes('ml-8')


def _is_method_or_property(cls: type, attribute_name: str) -> bool:
attribute = cls.__dict__.get(attribute_name, None)
return (
Expand Down