|
1 | 1 | # Copyright (c) Jupyter Development Team.
|
2 | 2 | # Distributed under the terms of the Modified BSD License.
|
3 | 3 |
|
4 |
| -import ipywidgets as widgets |
5 |
| -from ipywidgets.widgets.widget_link import Link |
| 4 | +import argparse |
| 5 | +import json |
| 6 | +from operator import itemgetter |
6 | 7 |
|
7 |
| -from traitlets import CaselessStrEnum, Unicode, Tuple, List, Bool, CFloat, Float, CInt, Int, Instance, Dict, Any |
| 8 | +from traitlets import (CaselessStrEnum, Unicode, Tuple, List, Bool, CFloat, |
| 9 | + Float, CInt, Int, Instance, Dict, Bytes, Any) |
| 10 | + |
| 11 | +import ipywidgets as widgets |
8 | 12 | from ipywidgets import Color
|
9 | 13 | from ipywidgets.widgets.trait_types import TypedTuple
|
| 14 | +from ipywidgets.widgets.widget_link import Link |
10 | 15 |
|
11 |
| -header = '''# Model State |
| 16 | +HEADER = '''# Model State |
12 | 17 |
|
13 |
| -This is a description of the model state for each widget in the core Jupyter widgets library. The model ID of a widget is the id of the comm object the widget is using. A reference to a widget is serialized to JSON as a string of the form `"IPY_MODEL_<MODEL_ID>"`, where `<MODEL_ID>` is the model ID of a previously created widget of the specified type. |
| 18 | +This is a description of the model state for each widget in the core Jupyter |
| 19 | +widgets library. The model ID of a widget is the id of the comm object the |
| 20 | +widget is using. A reference to a widget is serialized to JSON as a string of |
| 21 | +the form `"IPY_MODEL_<MODEL_ID>"`, where `<MODEL_ID>` is the model ID of a |
| 22 | +previously created widget of the specified type. |
14 | 23 |
|
15 |
| -This model specification is for ipywidgets 7.4.*, @jupyter-widgets/base 1.1.*, and @jupyter-widgets/controls 1.4.*. |
| 24 | +This model specification is for ipywidgets 7.4.*, @jupyter-widgets/base 1.1.*, |
| 25 | +and @jupyter-widgets/controls 1.4.*. |
16 | 26 |
|
17 | 27 | ## Model attributes
|
18 | 28 |
|
19 |
| -Each widget in the Jupyter core widgets is represented below. The heading represents the model name, module, and version, view name, module, and version that the widget is registered with. |
| 29 | +Each widget in the Jupyter core widgets is represented below. The heading |
| 30 | +represents the model name, module, and version, view name, module, and version |
| 31 | +that the widget is registered with. |
20 | 32 |
|
21 | 33 | '''
|
22 | 34 |
|
23 |
| -widgets_to_document = sorted(widgets.Widget.widget_types.items()) |
24 |
| - |
25 |
| -def typing(x): |
26 |
| - s = '' |
27 |
| - if isinstance(x, CaselessStrEnum): |
28 |
| - s = 'string (one of %s)'%(', '.join('`%r`'%i for i in x.values)) |
29 |
| - elif isinstance(x, Unicode): |
30 |
| - s = 'string' |
31 |
| - elif isinstance(x, (Tuple, List)): |
32 |
| - s = 'array' |
33 |
| - elif isinstance(x, TypedTuple): |
34 |
| - s = 'array of ' + typing(x._trait) |
35 |
| - elif isinstance(x, Bool): |
36 |
| - s = 'boolean' |
37 |
| - elif isinstance(x, (CFloat, Float)): |
38 |
| - s = 'number (float)' |
39 |
| - elif isinstance(x, (CInt, Int)): |
40 |
| - s = 'number (integer)' |
41 |
| - elif isinstance(x, Color): |
42 |
| - s = 'string (valid color)' |
43 |
| - elif isinstance(x, Dict): |
44 |
| - s = 'object' |
45 |
| - elif isinstance(x, Instance) and issubclass(x.klass, widgets.Widget): |
46 |
| - s = 'reference to %s widget'%(x.klass.__name__) |
| 35 | +NUMBER_MAP = { |
| 36 | + 'int': 'number (integer)', |
| 37 | + 'float': 'number (float)', |
| 38 | + 'bool': 'boolean', |
| 39 | + 'bytes': 'Bytes' |
| 40 | +} |
| 41 | + |
| 42 | + |
| 43 | +def widget_type(widget, widget_list): |
| 44 | + attributes = {} |
| 45 | + if isinstance(widget, CaselessStrEnum): |
| 46 | + w_type = 'string' |
| 47 | + attributes['enum'] = widget.values |
| 48 | + elif isinstance(widget, Unicode): |
| 49 | + w_type = 'string' |
| 50 | + elif isinstance(widget, (Tuple, List)): |
| 51 | + w_type = 'array' |
| 52 | + elif isinstance(widget, TypedTuple): |
| 53 | + w_type = 'array' |
| 54 | + attributes['items'] = widget_type(widget._trait, widget_list) |
| 55 | + elif isinstance(widget, Bool): |
| 56 | + w_type = 'bool' |
| 57 | + elif isinstance(widget, (CFloat, Float)): |
| 58 | + w_type = 'float' |
| 59 | + elif isinstance(widget, (CInt, Int)): |
| 60 | + w_type = 'int' |
| 61 | + elif isinstance(widget, Color): |
| 62 | + w_type = 'color' |
| 63 | + elif isinstance(widget, Dict): |
| 64 | + w_type = 'object' |
| 65 | + elif isinstance(widget, Bytes): |
| 66 | + w_type = 'bytes' |
| 67 | + elif isinstance(widget, Instance) and issubclass(widget.klass, |
| 68 | + widgets.Widget): |
| 69 | + w_type = 'reference' |
| 70 | + attributes['widget'] = widget.klass.__name__ |
47 | 71 | # ADD the widget to this documenting list
|
48 |
| - if x.klass not in [i[1] for i in widgets_to_document] and x.klass != widgets.Widget: |
49 |
| - widgets_to_document.append((x.klass.__name__, x.klass)) |
50 |
| - elif isinstance(x, Any): |
51 |
| - # In our case, these all happen to be values that are converted to strings |
52 |
| - s = 'string (valid option label)' |
| 72 | + if (widget.klass not in [i[1] for i in widget_list] |
| 73 | + and widget.klass is not widgets.Widget): |
| 74 | + widget_list.append((widget.klass.__name__, widget.klass)) |
| 75 | + elif isinstance(widget, Any): |
| 76 | + # In our case, these all happen to be values that are converted to |
| 77 | + # strings |
| 78 | + w_type = 'label' |
53 | 79 | else:
|
54 |
| - s = x.__class__.__name__ |
55 |
| - if x.allow_none: |
56 |
| - s = "`null` or "+s |
57 |
| - return s |
58 |
| - |
59 |
| -def jsdefault(t): |
60 |
| - x = t.default_value |
61 |
| - if isinstance(t, Instance): |
62 |
| - x = t.make_dynamic_default() |
63 |
| - if issubclass(t.klass, widgets.Widget): |
| 80 | + w_type = widget.__class__.__name__ |
| 81 | + attributes['type'] = w_type |
| 82 | + if widget.allow_none: |
| 83 | + attributes['allow_none'] = True |
| 84 | + return attributes |
| 85 | + |
| 86 | + |
| 87 | +def jsdefault(trait): |
| 88 | + if isinstance(trait, Instance): |
| 89 | + default = trait.make_dynamic_default() |
| 90 | + if issubclass(trait.klass, widgets.Widget): |
64 | 91 | return 'reference to new instance'
|
65 |
| - if x is True: |
66 |
| - return '`true`' |
67 |
| - elif x is False: |
68 |
| - return '`false`' |
69 |
| - elif x is None: |
70 |
| - return '`null`' |
71 |
| - elif isinstance(x, tuple): |
72 |
| - return '`{}`'.format(list(x)) |
73 | 92 | else:
|
74 |
| - return '`%s`'%t.default_value_repr() |
| 93 | + default = trait.default_value |
| 94 | + if isinstance(default, bytes): |
| 95 | + default = trait.default_value_repr() |
| 96 | + return default |
| 97 | + |
| 98 | + |
| 99 | +def mddefault(attribute): |
| 100 | + default = attribute['default'] |
| 101 | + is_ref = isinstance(default, str) and default.startswith('reference') |
| 102 | + if default is None: |
| 103 | + default = 'null' |
| 104 | + elif isinstance(default, bool): |
| 105 | + default = str(default).lower() |
| 106 | + elif not is_ref and attribute['type'] != 'bytes': |
| 107 | + default = "{!r}".format(default) |
| 108 | + if not is_ref: |
| 109 | + default = '`{}`'.format(default) |
| 110 | + return default |
75 | 111 |
|
76 |
| -def format_widget(n, w): |
77 |
| - out = [] |
78 |
| - name = dict(zip(['m_module', 'm_version', 'model', 'v_module', 'v_version', 'view'], n)) |
79 | 112 |
|
80 |
| - out.append('### %(model)s (%(m_module)s, %(m_version)s); %(view)s (%(v_module)s, %(v_version)s)'%name) |
| 113 | +def mdtype(attribute): |
| 114 | + md_type = attribute['type'] |
| 115 | + if md_type in NUMBER_MAP: |
| 116 | + md_type = NUMBER_MAP[md_type] |
| 117 | + if attribute.get('allow_none'): |
| 118 | + md_type = '`null` or {}'.format(md_type) |
| 119 | + if 'enum' in attribute: |
| 120 | + md_type = '{} (one of {})'.format( |
| 121 | + md_type, ', '.join('`{!r}`'.format(n) for n in attribute['enum']) |
| 122 | + ) |
| 123 | + if 'items' in attribute: |
| 124 | + md_type = '{} of {}'.format(md_type, mdtype(attribute['items'])) |
| 125 | + if 'widget' in attribute: |
| 126 | + md_type = '{} to {} widget'.format(md_type, attribute['widget']) |
| 127 | + return md_type |
| 128 | + |
| 129 | + |
| 130 | +def format_widget(widget): |
| 131 | + out = [] |
| 132 | + fmt = '%(name)s (%(module)s, %(version)s)' |
| 133 | + out.append('### %s; %s' % (fmt % widget['model'], fmt % widget['view'])) |
81 | 134 | out.append('')
|
82 |
| - out.append('{name: <16} | {typing: <16} | {default: <16} | {help}'.format(name='Attribute', typing='Type', |
83 |
| - default='Default', help='Help')) |
| 135 | + out.append('{name: <16} | {typing: <16} | {default: <16} | {help}'.format( |
| 136 | + name='Attribute', typing='Type', default='Default', help='Help') |
| 137 | + ) |
84 | 138 | out.append('{0:-<16}-|-{0:-<16}-|-{0:-<16}-|----'.format('-'))
|
85 |
| - for name, t in sorted(w.traits(sync=True).items()): |
86 |
| - if name in ('_model_module', '_view_module', '_model_module_version', '_view_module_version', |
87 |
| - '_dom_classes', 'layout'): |
88 |
| - # document these separately, since they apply to all classes |
89 |
| - pass |
90 |
| - if name in ('_view_count'): |
91 |
| - # don't document this since it is totally experimental at this point |
92 |
| - continue |
93 | 139 |
|
94 |
| - s = '{name: <16} | {typing: <16} | {default: <16} | {help}'.format(name='`%s`'%name, typing=typing(t), |
95 |
| - allownone='*' if t.allow_none else '', |
96 |
| - default=jsdefault(t), |
97 |
| - help=t.help if t.help else '') |
| 140 | + for attribute in sorted(widget['attributes'], key=itemgetter('name')): |
| 141 | + s = '{name: <16} | {type: <16} | {default: <16} | {help}'.format( |
| 142 | + name='`{}`'.format(attribute['name']), |
| 143 | + default=mddefault(attribute), |
| 144 | + type=mdtype(attribute), |
| 145 | + help=attribute['help'] |
| 146 | + ) |
98 | 147 | out.append(s)
|
99 | 148 | out.append('')
|
100 | 149 | return '\n'.join(out)
|
101 | 150 |
|
102 |
| -out = header |
103 |
| -for n,w in widgets_to_document: |
104 |
| - if issubclass(w, Link): |
105 |
| - out += '\n'+format_widget(n, w((widgets.IntSlider(), 'value'), (widgets.IntSlider(), 'value'))) |
106 |
| - elif issubclass(w, widgets.SelectionRangeSlider) or issubclass(w, widgets.SelectionSlider): |
107 |
| - out += '\n'+format_widget(n,w(options=[1])) |
108 |
| - else: |
109 |
| - out += '\n'+format_widget(n,w()) |
110 |
| -print(out) |
| 151 | + |
| 152 | +def jsonify(identifier, widget, widget_list): |
| 153 | + model = dict(zip(['module', 'version', 'name'], identifier[:3])) |
| 154 | + view = dict(zip(['module', 'version', 'name'], identifier[3:])) |
| 155 | + attributes = [] |
| 156 | + for name, trait in widget.traits(sync=True).items(): |
| 157 | + if name == '_view_count': |
| 158 | + # don't document this since it is totally experimental at this point |
| 159 | + continue |
| 160 | + |
| 161 | + attribute = dict( |
| 162 | + name=name, |
| 163 | + help=trait.help or '', |
| 164 | + default=jsdefault(trait) |
| 165 | + ) |
| 166 | + attribute.update(widget_type(trait, widget_list)) |
| 167 | + attributes.append(attribute) |
| 168 | + |
| 169 | + return dict(model=model, view=view, attributes=attributes) |
| 170 | + |
| 171 | + |
| 172 | +def create_spec(widget_list): |
| 173 | + widget_data = [] |
| 174 | + for widget_name, widget_cls in widget_list: |
| 175 | + if issubclass(widget_cls, Link): |
| 176 | + widget = widget_cls((widgets.IntSlider(), 'value'), |
| 177 | + (widgets.IntSlider(), 'value')) |
| 178 | + elif issubclass(widget_cls, (widgets.SelectionRangeSlider, |
| 179 | + widgets.SelectionSlider)): |
| 180 | + widget = widget_cls(options=[1]) |
| 181 | + else: |
| 182 | + widget = widget_cls() |
| 183 | + |
| 184 | + widget_data.append(jsonify(widget_name, widget, widget_list)) |
| 185 | + return widget_data |
| 186 | + |
| 187 | + |
| 188 | +def create_markdown(spec): |
| 189 | + output = [HEADER] |
| 190 | + for widget in spec: |
| 191 | + output.append(format_widget(widget)) |
| 192 | + return '\n'.join(output) |
| 193 | + |
| 194 | + |
| 195 | +if __name__ == '__main__': |
| 196 | + parser = argparse.ArgumentParser(description='Description of your program') |
| 197 | + parser.add_argument('-f', '--format', choices=['json', 'json-pretty', 'markdown'], |
| 198 | + help='Format to generate', default='json') |
| 199 | + args = parser.parse_args() |
| 200 | + format = args.format |
| 201 | + |
| 202 | + widgets_to_document = sorted(widgets.Widget.widget_types.items()) |
| 203 | + spec = create_spec(widgets_to_document) |
| 204 | + if format == 'json': |
| 205 | + print(json.dumps(spec, sort_keys=True)) |
| 206 | + elif format == 'json-pretty': |
| 207 | + print(json.dumps(spec, sort_keys=True, |
| 208 | + indent=2, separators=(',', ': '))) |
| 209 | + elif format == 'markdown': |
| 210 | + # We go through the json engine to convert tuples to lists, etc. |
| 211 | + print(create_markdown(json.loads(json.dumps(spec)))) |
0 commit comments