reflex-docgen 0.9.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ """Module for generating documentation for Reflex components and classes."""
2
+
3
+ from reflex_docgen import markdown as markdown
4
+ from reflex_docgen._class import ClassDocumentation as ClassDocumentation
5
+ from reflex_docgen._class import FieldDocumentation as FieldDocumentation
6
+ from reflex_docgen._class import MethodDocumentation as MethodDocumentation
7
+ from reflex_docgen._class import (
8
+ generate_class_documentation as generate_class_documentation,
9
+ )
10
+ from reflex_docgen._component import ComponentDocumentation as ComponentDocumentation
11
+ from reflex_docgen._component import (
12
+ EventHandlerDocumentation as EventHandlerDocumentation,
13
+ )
14
+ from reflex_docgen._component import PropDocumentation as PropDocumentation
15
+ from reflex_docgen._component import generate_documentation as generate_documentation
16
+ from reflex_docgen._component import (
17
+ get_component_event_handlers as get_component_event_handlers,
18
+ )
19
+ from reflex_docgen._component import get_component_props as get_component_props
20
+
21
+ __all__ = [
22
+ "ClassDocumentation",
23
+ "ComponentDocumentation",
24
+ "EventHandlerDocumentation",
25
+ "FieldDocumentation",
26
+ "MethodDocumentation",
27
+ "PropDocumentation",
28
+ "generate_class_documentation",
29
+ "generate_documentation",
30
+ "get_component_event_handlers",
31
+ "get_component_props",
32
+ "markdown",
33
+ ]
@@ -0,0 +1,345 @@
1
+ """Generate documentation for arbitrary Python classes."""
2
+
3
+ import dataclasses
4
+ import inspect
5
+ from dataclasses import dataclass
6
+ from typing import Any, get_args, get_type_hints
7
+
8
+ from reflex_base.vars.base import BaseStateMeta
9
+ from typing_inspection.introspection import AnnotationSource, inspect_annotation
10
+
11
+
12
+ @dataclass(frozen=True, slots=True, kw_only=True)
13
+ class FieldDocumentation:
14
+ """Hold information about a class field.
15
+
16
+ Attributes:
17
+ name: The name of the field.
18
+ type: The resolved type of the field.
19
+ type_display: Human-readable type string (no Var wrapper). Uses __name__ for simple types, str() for generics.
20
+ description: The description extracted from the class docstring or field.doc.
21
+ default: The repr() of the default value, or None if no default.
22
+ """
23
+
24
+ name: str
25
+
26
+ type: Any
27
+
28
+ type_display: str
29
+
30
+ description: str | None
31
+
32
+ default: str | None
33
+
34
+
35
+ @dataclass(frozen=True, slots=True, kw_only=True)
36
+ class MethodDocumentation:
37
+ """Hold information about a class method.
38
+
39
+ Attributes:
40
+ name: The name of the method.
41
+ signature: The string representation of the method signature.
42
+ description: The docstring, truncated before 'Args:' or 'Returns:' sections.
43
+ """
44
+
45
+ name: str
46
+
47
+ signature: str
48
+
49
+ description: str | None
50
+
51
+
52
+ @dataclass(frozen=True, slots=True, kw_only=True)
53
+ class ClassDocumentation:
54
+ """Documentation for an arbitrary Python class.
55
+
56
+ Attributes:
57
+ name: The fully qualified name (module.qualname) of the class.
58
+ description: The cleaned docstring of the class.
59
+ fields: Instance fields (from dataclass fields or rx.State __fields__).
60
+ class_fields: Class variables (from __class_vars__ on rx.Base subclasses).
61
+ methods: Public methods that have docstrings.
62
+ """
63
+
64
+ name: str
65
+
66
+ description: str | None
67
+
68
+ fields: tuple[FieldDocumentation, ...] = ()
69
+
70
+ class_fields: tuple[FieldDocumentation, ...] = ()
71
+
72
+ methods: tuple[MethodDocumentation, ...] = ()
73
+
74
+
75
+ def _parse_docstring_attributes(cls: type) -> dict[str, str]:
76
+ """Parse an Attributes section from a class docstring using griffe.
77
+
78
+ Args:
79
+ cls: The class whose docstring to parse.
80
+
81
+ Returns:
82
+ A mapping from attribute name to description string.
83
+ """
84
+ from griffe import Docstring, Parser # provided by griffelib
85
+
86
+ doc = cls.__doc__
87
+ if not doc:
88
+ return {}
89
+
90
+ parsed = Docstring(inspect.cleandoc(doc)).parse(Parser.auto)
91
+ return {
92
+ attr.name: attr.description
93
+ for section in parsed
94
+ if section.kind.value == "attributes"
95
+ for attr in section.value
96
+ }
97
+
98
+
99
+ def _type_display(type_: Any) -> str:
100
+ """Return a human-readable type string.
101
+
102
+ Args:
103
+ type_: The type to display.
104
+
105
+ Returns:
106
+ A human-readable type string.
107
+ """
108
+ if get_args(type_):
109
+ return str(type_)
110
+ return getattr(type_, "__name__", str(type_))
111
+
112
+
113
+ def _extract_field_doc(hint: Any, field_doc: str | None) -> tuple[Any, str | None]:
114
+ """Extract the unwrapped type and description from a type hint.
115
+
116
+ Args:
117
+ hint: The type hint.
118
+ field_doc: The field.doc attribute value.
119
+
120
+ Returns:
121
+ A tuple of (unwrapped_type, description).
122
+ """
123
+ inspected = inspect_annotation(hint, annotation_source=AnnotationSource.ANY)
124
+ return inspected.type, field_doc
125
+
126
+
127
+ def _build_field_documentation(
128
+ name: str,
129
+ hint: Any,
130
+ field_doc: str | None,
131
+ default_value: str | None,
132
+ docstring_desc: str | None = None,
133
+ ) -> FieldDocumentation | None:
134
+ """Build a FieldDocumentation from field info, or None if private.
135
+
136
+ Args:
137
+ name: The field name.
138
+ hint: The type hint.
139
+ field_doc: The field.doc attribute value.
140
+ default_value: The repr'd default value, or None.
141
+ docstring_desc: Description from the class docstring Attributes section (fallback).
142
+
143
+ Returns:
144
+ A FieldDocumentation, or None if the field should be skipped.
145
+ """
146
+ if name.startswith("_"):
147
+ return None
148
+
149
+ unwrapped_type, description = _extract_field_doc(hint, field_doc)
150
+
151
+ # Fall back to docstring Attributes section if no description found.
152
+ if description is None and docstring_desc is not None:
153
+ description = docstring_desc
154
+
155
+ if description is not None and "PRIVATE" in description:
156
+ return None
157
+
158
+ return FieldDocumentation(
159
+ name=name,
160
+ type=unwrapped_type,
161
+ type_display=_type_display(unwrapped_type),
162
+ description=description,
163
+ default=default_value,
164
+ )
165
+
166
+
167
+ def _get_dataclass_fields(cls: type) -> tuple[FieldDocumentation, ...]:
168
+ """Extract fields from a dataclass.
169
+
170
+ Args:
171
+ cls: The dataclass to extract fields from.
172
+
173
+ Returns:
174
+ A tuple of FieldDocumentation objects.
175
+ """
176
+ hints = get_type_hints(cls, include_extras=True)
177
+ docstring_attrs = _parse_docstring_attributes(cls)
178
+ result = []
179
+ for f in dataclasses.fields(cls):
180
+ hint = hints.get(f.name, f.type)
181
+ field_doc = getattr(f, "doc", None)
182
+
183
+ if f.default is not dataclasses.MISSING:
184
+ default_str = repr(f.default)
185
+ elif f.default_factory is not dataclasses.MISSING:
186
+ default_str = repr(f.default_factory)
187
+ else:
188
+ default_str = None
189
+
190
+ doc = _build_field_documentation(
191
+ f.name, hint, field_doc, default_str, docstring_attrs.get(f.name)
192
+ )
193
+ if doc is not None:
194
+ result.append(doc)
195
+
196
+ return tuple(result)
197
+
198
+
199
+ def _get_state_fields(cls: BaseStateMeta) -> tuple[FieldDocumentation, ...]:
200
+ """Extract instance fields from an rx.State subclass via __fields__.
201
+
202
+ Args:
203
+ cls: The class to extract fields from.
204
+
205
+ Returns:
206
+ A tuple of FieldDocumentation objects.
207
+ """
208
+ hints = get_type_hints(cls, include_extras=True)
209
+ docstring_attrs = _parse_docstring_attributes(cls)
210
+ fields_dict = cls.__fields__
211
+ result = []
212
+ for name, field in fields_dict.items():
213
+ hint = hints.get(name, field.outer_type_)
214
+
215
+ if field.default is not dataclasses.MISSING:
216
+ default_str = repr(field.default)
217
+ elif field.default_factory is not None:
218
+ default_str = repr(field.default_factory)
219
+ else:
220
+ default_str = None
221
+
222
+ doc = _build_field_documentation(
223
+ name, hint, None, default_str, docstring_attrs.get(name)
224
+ )
225
+ if doc is not None:
226
+ result.append(doc)
227
+
228
+ return tuple(result)
229
+
230
+
231
+ def _get_class_vars(cls: type) -> tuple[FieldDocumentation, ...]:
232
+ """Extract class variables from __class_vars__.
233
+
234
+ Args:
235
+ cls: The class to extract class variables from.
236
+
237
+ Returns:
238
+ A tuple of FieldDocumentation objects.
239
+ """
240
+ class_vars = getattr(cls, "__class_vars__", None)
241
+ if not class_vars:
242
+ return ()
243
+
244
+ hints = get_type_hints(cls, include_extras=True)
245
+ docstring_attrs = _parse_docstring_attributes(cls)
246
+ result = []
247
+ for name in class_vars:
248
+ hint = hints.get(name, type(None))
249
+ doc = _build_field_documentation(
250
+ name, hint, None, None, docstring_attrs.get(name)
251
+ )
252
+ if doc is not None:
253
+ result.append(doc)
254
+
255
+ return tuple(result)
256
+
257
+
258
+ def _get_methods(cls: type) -> tuple[MethodDocumentation, ...]:
259
+ """Extract public documented methods from a class.
260
+
261
+ Args:
262
+ cls: The class to extract methods from.
263
+
264
+ Returns:
265
+ A tuple of MethodDocumentation objects.
266
+ """
267
+ result = []
268
+ for name, obj in cls.__dict__.items():
269
+ if name.startswith("_") or name == "Config":
270
+ continue
271
+
272
+ fn = obj
273
+ if isinstance(obj, (classmethod, staticmethod)):
274
+ fn = obj.__func__
275
+
276
+ if not callable(fn):
277
+ continue
278
+
279
+ docstring = getattr(fn, "__doc__", None)
280
+ if not docstring:
281
+ continue
282
+
283
+ # Truncate docstring before Args: or Returns:
284
+ for marker in ("Args:", "Returns:"):
285
+ idx = docstring.find(marker)
286
+ if idx != -1:
287
+ docstring = docstring[:idx]
288
+ break
289
+ docstring = docstring.strip() or None
290
+
291
+ try:
292
+ sig = str(inspect.signature(fn))
293
+ except (ValueError, TypeError):
294
+ sig = "(...)"
295
+
296
+ result.append(
297
+ MethodDocumentation(
298
+ name=name,
299
+ signature=sig,
300
+ description=docstring,
301
+ )
302
+ )
303
+
304
+ return tuple(result)
305
+
306
+
307
+ def generate_class_documentation(cls: type) -> ClassDocumentation:
308
+ """Generate documentation for an arbitrary Python class.
309
+
310
+ Supports dataclasses, rx.State subclasses, and other classes (methods only).
311
+
312
+ Args:
313
+ cls: The class to generate documentation for.
314
+
315
+ Returns:
316
+ The generated documentation for the class.
317
+ """
318
+ try:
319
+ description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None
320
+
321
+ if dataclasses.is_dataclass(cls):
322
+ fields = _get_dataclass_fields(cls)
323
+ elif isinstance(cls, BaseStateMeta):
324
+ fields = _get_state_fields(cls)
325
+ else:
326
+ fields = ()
327
+
328
+ class_fields = _get_class_vars(cls)
329
+ methods = _get_methods(cls)
330
+
331
+ return ClassDocumentation(
332
+ name=f"{cls.__module__}.{cls.__qualname__}",
333
+ description=description,
334
+ fields=fields,
335
+ class_fields=class_fields,
336
+ methods=methods,
337
+ )
338
+ except Exception as e:
339
+ import sys
340
+
341
+ if sys.version_info >= (3, 11):
342
+ e.add_note(
343
+ f"Error generating documentation for class {cls.__module__}.{cls.__qualname__}"
344
+ )
345
+ raise
@@ -0,0 +1,151 @@
1
+ """Generate documentation for Reflex components."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from reflex_base.components.component import DEFAULT_TRIGGERS_AND_DESC, Component
7
+ from reflex_base.event import EventHandler
8
+
9
+
10
+ @dataclass(frozen=True, slots=True, kw_only=True)
11
+ class PropDocumentation:
12
+ """Hold information about a prop.
13
+
14
+ Attributes:
15
+ name: The name of the prop.
16
+ type: The type of the prop.
17
+ description: The description of the prop.
18
+ default_value: The default value of the prop.
19
+ """
20
+
21
+ name: str
22
+
23
+ type: Any
24
+
25
+ description: str | None
26
+
27
+ default_value: str | None
28
+
29
+
30
+ @dataclass(frozen=True, slots=True, kw_only=True)
31
+ class EventHandlerDocumentation:
32
+ """Hold information about an event handler.
33
+
34
+ Attributes:
35
+ name: The name of the event handler.
36
+ description: The description of the event handler.
37
+ is_inherited: Whether the event handler is inherited from DEFAULT_TRIGGERS.
38
+ """
39
+
40
+ name: str
41
+
42
+ description: str | None
43
+
44
+ is_inherited: bool
45
+
46
+
47
+ @dataclass(frozen=True, slots=True, kw_only=True)
48
+ class ComponentDocumentation:
49
+ """Documentation for a Reflex component.
50
+
51
+ Attributes:
52
+ name: The name of the component.
53
+ description: The docstring of the component class.
54
+ props: The list of props for the component.
55
+ event_handlers: The list of event handlers for the component.
56
+ """
57
+
58
+ name: str
59
+ description: str | None = None
60
+ props: tuple[PropDocumentation, ...] = ()
61
+ event_handlers: tuple[EventHandlerDocumentation, ...] = ()
62
+
63
+
64
+ def get_component_props(
65
+ component_cls: type[Component],
66
+ ) -> tuple[PropDocumentation, ...]:
67
+ """Get the props for a Reflex component.
68
+
69
+ Args:
70
+ component_cls: The component class to get the props for.
71
+
72
+ Returns:
73
+ The props for the component.
74
+ """
75
+ props = component_cls.get_js_fields()
76
+
77
+ result = []
78
+ for prop_name, component_field in props.items():
79
+ if component_field.type_origin is EventHandler:
80
+ continue
81
+ doc = component_field.doc
82
+ default_value = None
83
+
84
+ # If the field has a doc attribute, use it as the description.
85
+ if doc is not None:
86
+ for default_indicator in ["Defaults to", "Default:"]:
87
+ if default_indicator in doc:
88
+ doc, default_value = doc.split(default_indicator, maxsplit=1)
89
+ default_value = default_value.strip().rstrip(".")
90
+ doc = doc.strip().rstrip(".")
91
+ break
92
+
93
+ result.append(
94
+ PropDocumentation(
95
+ name=prop_name,
96
+ type=component_field.type_,
97
+ description=doc.rstrip(".") + "." if doc else None,
98
+ default_value=default_value,
99
+ )
100
+ )
101
+
102
+ return tuple(result)
103
+
104
+
105
+ def get_component_event_handlers(
106
+ component_cls: type[Component],
107
+ ) -> tuple[EventHandlerDocumentation, ...]:
108
+ """Get the event handlers for a Reflex component.
109
+
110
+ Args:
111
+ component_cls: The component class to get the event handlers for.
112
+
113
+ Returns:
114
+ The event handlers for the component.
115
+ """
116
+ event_triggers = component_cls.get_event_triggers()
117
+ fields = component_cls.get_fields()
118
+
119
+ return tuple(
120
+ EventHandlerDocumentation(
121
+ name=name,
122
+ description=(
123
+ field.doc.rstrip(".") + "."
124
+ if (field := fields.get(name)) is not None and field.doc
125
+ else (
126
+ DEFAULT_TRIGGERS_AND_DESC[name].description
127
+ if name in DEFAULT_TRIGGERS_AND_DESC
128
+ else None
129
+ )
130
+ ),
131
+ is_inherited=name in DEFAULT_TRIGGERS_AND_DESC,
132
+ )
133
+ for name in event_triggers
134
+ )
135
+
136
+
137
+ def generate_documentation(component_cls: type[Component]) -> ComponentDocumentation:
138
+ """Generate documentation for a Reflex component.
139
+
140
+ Args:
141
+ component_cls: The component class to generate documentation for.
142
+
143
+ Returns:
144
+ The generated documentation for the component.
145
+ """
146
+ return ComponentDocumentation(
147
+ name=component_cls.__name__,
148
+ description=component_cls.__doc__,
149
+ props=get_component_props(component_cls),
150
+ event_handlers=get_component_event_handlers(component_cls),
151
+ )
@@ -0,0 +1,57 @@
1
+ """Markdown parsing and types for Reflex documentation."""
2
+
3
+ from reflex_docgen.markdown import transformer as transformer
4
+ from reflex_docgen.markdown._parser import parse_document as parse_document
5
+ from reflex_docgen.markdown._types import Block as Block
6
+ from reflex_docgen.markdown._types import BoldSpan as BoldSpan
7
+ from reflex_docgen.markdown._types import CodeBlock as CodeBlock
8
+ from reflex_docgen.markdown._types import CodeSpan as CodeSpan
9
+ from reflex_docgen.markdown._types import ComponentPreview as ComponentPreview
10
+ from reflex_docgen.markdown._types import DirectiveBlock as DirectiveBlock
11
+ from reflex_docgen.markdown._types import Document as Document
12
+ from reflex_docgen.markdown._types import FrontMatter as FrontMatter
13
+ from reflex_docgen.markdown._types import HeadingBlock as HeadingBlock
14
+ from reflex_docgen.markdown._types import ImageSpan as ImageSpan
15
+ from reflex_docgen.markdown._types import ItalicSpan as ItalicSpan
16
+ from reflex_docgen.markdown._types import LineBreakSpan as LineBreakSpan
17
+ from reflex_docgen.markdown._types import LinkSpan as LinkSpan
18
+ from reflex_docgen.markdown._types import ListBlock as ListBlock
19
+ from reflex_docgen.markdown._types import ListItem as ListItem
20
+ from reflex_docgen.markdown._types import QuoteBlock as QuoteBlock
21
+ from reflex_docgen.markdown._types import Span as Span
22
+ from reflex_docgen.markdown._types import StrikethroughSpan as StrikethroughSpan
23
+ from reflex_docgen.markdown._types import TableBlock as TableBlock
24
+ from reflex_docgen.markdown._types import TableCell as TableCell
25
+ from reflex_docgen.markdown._types import TableRow as TableRow
26
+ from reflex_docgen.markdown._types import TextBlock as TextBlock
27
+ from reflex_docgen.markdown._types import TextSpan as TextSpan
28
+ from reflex_docgen.markdown._types import ThematicBreakBlock as ThematicBreakBlock
29
+
30
+ __all__ = [
31
+ "Block",
32
+ "BoldSpan",
33
+ "CodeBlock",
34
+ "CodeSpan",
35
+ "ComponentPreview",
36
+ "DirectiveBlock",
37
+ "Document",
38
+ "FrontMatter",
39
+ "HeadingBlock",
40
+ "ImageSpan",
41
+ "ItalicSpan",
42
+ "LineBreakSpan",
43
+ "LinkSpan",
44
+ "ListBlock",
45
+ "ListItem",
46
+ "QuoteBlock",
47
+ "Span",
48
+ "StrikethroughSpan",
49
+ "TableBlock",
50
+ "TableCell",
51
+ "TableRow",
52
+ "TextBlock",
53
+ "TextSpan",
54
+ "ThematicBreakBlock",
55
+ "parse_document",
56
+ "transformer",
57
+ ]