reference.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import inspect
  2. from typing import Callable, Optional
  3. from nicegui import binding, ui
  4. from nicegui.elements.markdown import remove_indentation
  5. from ..style import create_anchor_name, subheading
  6. from .custom_restructured_text import CustomRestructuredText as custom_restructured_text
  7. def generate_class_doc(class_obj: type, part_title: str) -> None:
  8. """Generate documentation for a class including all its methods and properties."""
  9. doc = class_obj.__doc__ or class_obj.__init__.__doc__
  10. if doc and ':param' in doc:
  11. subheading('Initializer', anchor_name=create_anchor_name(part_title.replace('Reference', 'Initializer')))
  12. description = remove_indentation(doc.split('\n', 1)[-1])
  13. lines = [line.replace(':param ', ':') for line in description.splitlines() if ':param' in line]
  14. custom_restructured_text('\n'.join(lines)).classes('bold-links arrow-links rst-param-tables')
  15. mro = [base for base in class_obj.__mro__ if base.__module__.startswith('nicegui.')]
  16. ancestors = mro[1:]
  17. attributes = {}
  18. for base in reversed(mro):
  19. for name in dir(base):
  20. if not name.startswith('_') and _is_method_or_property(base, name):
  21. attributes[name] = getattr(base, name, None)
  22. properties = {name: attribute for name, attribute in attributes.items() if not callable(attribute)}
  23. methods = {name: attribute for name, attribute in attributes.items() if callable(attribute)}
  24. if properties:
  25. subheading('Properties', anchor_name=create_anchor_name(part_title.replace('Reference', 'Properties')))
  26. with ui.column().classes('gap-2'):
  27. for name, property_ in sorted(properties.items()):
  28. ui.markdown(f'**`{name}`**`{_generate_property_signature_description(property_)}`')
  29. if property_.__doc__:
  30. _render_docstring(property_.__doc__).classes('ml-8')
  31. if methods:
  32. subheading('Methods', anchor_name=create_anchor_name(part_title.replace('Reference', 'Methods')))
  33. with ui.column().classes('gap-2'):
  34. for name, method in sorted(methods.items()):
  35. decorator = ''
  36. if isinstance(class_obj.__dict__.get(name), staticmethod):
  37. decorator += '`@staticmethod`<br />'
  38. if isinstance(class_obj.__dict__.get(name), classmethod):
  39. decorator += '`@classmethod`<br />'
  40. ui.markdown(f'{decorator}**`{name}`**`{_generate_method_signature_description(method)}`')
  41. if method.__doc__:
  42. _render_docstring(method.__doc__).classes('ml-8')
  43. if ancestors:
  44. subheading('Inheritance', anchor_name=create_anchor_name(part_title.replace('Reference', 'Inheritance')))
  45. ui.markdown('\n'.join(f'- `{ancestor.__name__}`' for ancestor in ancestors))
  46. def _is_method_or_property(cls: type, attribute_name: str) -> bool:
  47. attribute = cls.__dict__.get(attribute_name, None)
  48. return (
  49. inspect.isfunction(attribute) or
  50. inspect.ismethod(attribute) or
  51. isinstance(attribute, (
  52. staticmethod,
  53. classmethod,
  54. property,
  55. binding.BindableProperty,
  56. ))
  57. )
  58. def _generate_property_signature_description(property_: Optional[property]) -> str:
  59. description = ''
  60. if property_ is None:
  61. return ': BindableProperty'
  62. if property_.fget:
  63. return_annotation = inspect.signature(property_.fget).return_annotation
  64. if return_annotation != inspect.Parameter.empty:
  65. return_type = inspect.formatannotation(return_annotation)
  66. description += f': {return_type}'
  67. if property_.fset:
  68. description += ' (settable)'
  69. if property_.fdel:
  70. description += ' (deletable)'
  71. return description
  72. def _generate_method_signature_description(method: Callable) -> str:
  73. param_strings = []
  74. for param in inspect.signature(method).parameters.values():
  75. param_string = param.name
  76. if param_string == 'self':
  77. continue
  78. if param.annotation != inspect.Parameter.empty:
  79. param_type = inspect.formatannotation(param.annotation)
  80. param_string += f''': {param_type.strip("'")}'''
  81. if param.default != inspect.Parameter.empty:
  82. param_string += ' = [...]' if callable(param.default) else f' = {param.default!r}'
  83. if param.kind == inspect.Parameter.VAR_POSITIONAL:
  84. param_string = f'*{param_string}'
  85. param_strings.append(param_string)
  86. method_signature = ', '.join(param_strings)
  87. description = f'({method_signature})'
  88. return_annotation = inspect.signature(method).return_annotation
  89. if return_annotation != inspect.Parameter.empty:
  90. return_type = inspect.formatannotation(return_annotation)
  91. description += f''' -> {return_type.strip("'").replace("typing_extensions.", "").replace("typing.", "")}'''
  92. return description
  93. def _render_docstring(doc: str) -> custom_restructured_text:
  94. doc = _remove_indentation_from_docstring(doc)
  95. return custom_restructured_text(doc).classes('bold-links arrow-links rst-param-tables')
  96. def _remove_indentation_from_docstring(text: str) -> str:
  97. lines = text.splitlines()
  98. if not lines:
  99. return ''
  100. if len(lines) == 1:
  101. return lines[0]
  102. indentation = min(len(line) - len(line.lstrip()) for line in lines[1:] if line.strip())
  103. return lines[0] + '\n' + '\n'.join(line[indentation:] for line in lines[1:])