reference.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import inspect
  2. import re
  3. from typing import Callable, Optional
  4. import docutils.core
  5. from nicegui import binding, ui
  6. from nicegui.elements.markdown import apply_tailwind, remove_indentation
  7. from ..style import subheading
  8. def generate_class_doc(class_obj: type) -> None:
  9. """Generate documentation for a class including all its methods and properties."""
  10. mro = [base for base in class_obj.__mro__ if base.__module__.startswith('nicegui.')]
  11. ancestors = mro[1:]
  12. attributes = {}
  13. for base in reversed(mro):
  14. for name in dir(base):
  15. if not name.startswith('_') and _is_method_or_property(base, name):
  16. attributes[name] = getattr(base, name, None)
  17. properties = {name: attribute for name, attribute in attributes.items() if not callable(attribute)}
  18. methods = {name: attribute for name, attribute in attributes.items() if callable(attribute)}
  19. if properties:
  20. subheading('Properties')
  21. with ui.column().classes('gap-2'):
  22. for name, property_ in sorted(properties.items()):
  23. ui.markdown(f'**`{name}`**`{_generate_property_signature_description(property_)}`')
  24. if property_.__doc__:
  25. _render_docstring(property_.__doc__).classes('ml-8')
  26. if methods:
  27. subheading('Methods')
  28. with ui.column().classes('gap-2'):
  29. for name, method in sorted(methods.items()):
  30. ui.markdown(f'**`{name}`**`{_generate_method_signature_description(method)}`')
  31. if method.__doc__:
  32. _render_docstring(method.__doc__).classes('ml-8')
  33. if ancestors:
  34. subheading('Inherited from')
  35. with ui.column().classes('gap-2'):
  36. for ancestor in ancestors:
  37. ui.markdown(f'- `{ancestor.__name__}`')
  38. def _is_method_or_property(cls: type, attribute_name: str) -> bool:
  39. attribute = cls.__dict__.get(attribute_name, None)
  40. return (
  41. inspect.isfunction(attribute) or
  42. inspect.ismethod(attribute) or
  43. isinstance(attribute, (property, binding.BindableProperty))
  44. )
  45. def _generate_property_signature_description(property_: Optional[property]) -> str:
  46. description = ''
  47. if property_ is None:
  48. return ': BindableProperty'
  49. if property_.fget:
  50. return_annotation = inspect.signature(property_.fget).return_annotation
  51. if return_annotation != inspect.Parameter.empty:
  52. return_type = inspect.formatannotation(return_annotation)
  53. description += f': {return_type}'
  54. if property_.fset:
  55. description += ' (settable)'
  56. if property_.fdel:
  57. description += ' (deletable)'
  58. return description
  59. def _generate_method_signature_description(method: Callable) -> str:
  60. param_strings = []
  61. for param in inspect.signature(method).parameters.values():
  62. param_string = param.name
  63. if param_string == 'self':
  64. continue
  65. if param.annotation != inspect.Parameter.empty:
  66. param_type = inspect.formatannotation(param.annotation)
  67. param_string += f''': {param_type.strip("'")}'''
  68. if param.default != inspect.Parameter.empty:
  69. param_string += ' = [...]' if callable(param.default) else f' = {repr(param.default)}'
  70. if param.kind == inspect.Parameter.VAR_POSITIONAL:
  71. param_string = f'*{param_string}'
  72. param_strings.append(param_string)
  73. method_signature = ', '.join(param_strings)
  74. description = f'({method_signature})'
  75. return_annotation = inspect.signature(method).return_annotation
  76. if return_annotation != inspect.Parameter.empty:
  77. return_type = inspect.formatannotation(return_annotation)
  78. description += f''' -> {return_type.strip("'").replace("typing_extensions.", "").replace("typing.", "")}'''
  79. return description
  80. def _render_docstring(doc: str, with_params: bool = True) -> ui.html:
  81. doc = remove_indentation(doc)
  82. doc = doc.replace('param ', '')
  83. html = docutils.core.publish_parts(doc, writer_name='html5_polyglot')['html_body']
  84. html = apply_tailwind(html)
  85. if not with_params:
  86. html = re.sub(r'<dl class=".* simple">.*?</dl>', '', html, flags=re.DOTALL)
  87. return ui.html(html).classes('documentation bold-links arrow-links')