Skip to content

Commit 17a0e5b

Browse files
committed
Support for wildcard attributes, implement data-* attribute
1 parent ff93d2c commit 17a0e5b

File tree

5 files changed

+93
-19
lines changed

5 files changed

+93
-19
lines changed

dash/development/base_component.py

+48-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ def __init__(self, **kwargs):
2323
# pylint: disable=super-init-not-called
2424
for k, v in list(kwargs.items()):
2525
# pylint: disable=no-member
26-
if k not in self._prop_names:
26+
k_in_propnames = k in self._prop_names
27+
k_in_wildcards = any([k.startswith(wc_attr)
28+
for wc_attr in
29+
self._valid_wildcard_attr_names])
30+
if not k_in_propnames and not k_in_wildcards:
2731
raise TypeError(
2832
'Unexpected keyword argument `{}`'.format(k) +
2933
'\nAllowed arguments: {}'.format(
@@ -225,6 +229,7 @@ def __init__(self, {default_argtext}):
225229
self._prop_names = {list_of_valid_keys}
226230
self._type = '{typename}'
227231
self._namespace = '{namespace}'
232+
self._valid_wildcard_attr_names={list_of_valid_wildcard_attr_prefixes}
228233
self.available_events = {events}
229234
self.available_properties = {list_of_valid_keys}
230235
@@ -236,14 +241,24 @@ def __init__(self, {default_argtext}):
236241
super({typename}, self).__init__({argtext})
237242
238243
def __repr__(self):
239-
if(any(getattr(self, c, None) is not None for c in self._prop_names
240-
if c is not self._prop_names[0])):
241-
242-
return (
243-
'{typename}(' +
244-
', '.join([c+'='+repr(getattr(self, c, None))
245-
for c in self._prop_names
246-
if getattr(self, c, None) is not None])+')')
244+
if(any(getattr(self, c, None) is not None
245+
for c in self._prop_names
246+
if c is not self._prop_names[0])
247+
or any(getattr(self, c, None) is not None
248+
for c in self.__dict__.keys()
249+
if any(c.startswith(wc_attr) for wc_attr in
250+
self._valid_wildcard_attr_names))):
251+
props_string = ', '.join([c+'='+repr(getattr(self, c, None))
252+
for c in self._prop_names
253+
if getattr(self, c, None) is not None])
254+
wilds_string = ', '.join([c+'='+repr(getattr(self, c, None))
255+
for c in self.__dict__.keys()
256+
if any([c.startswith(wc_attr)
257+
for wc_attr in
258+
self._valid_wildcard_attr_names])])
259+
260+
return ('{typename}(' + props_string +
261+
(', ' + wilds_string if wilds_string != '' else '') + ')')
247262
248263
else:
249264
return (
@@ -253,6 +268,8 @@ def __repr__(self):
253268

254269
filtered_props = reorder_props(filter_props(props))
255270
# pylint: disable=unused-variable
271+
list_of_valid_wildcard_attr_prefixes = repr(parse_wildcards(props))
272+
# pylint: disable=unused-variable
256273
list_of_valid_keys = repr(list(filtered_props.keys()))
257274
# pylint: disable=unused-variable
258275
docstring = create_docstring(
@@ -273,11 +290,9 @@ def __repr__(self):
273290

274291
required_args = required_props(props)
275292

276-
d = c.format(**locals())
277-
278293
scope = {'Component': Component}
279294
# pylint: disable=exec-used
280-
exec(d, scope)
295+
exec(c.format(**locals()), scope)
281296
result = scope[typename]
282297
return result
283298

@@ -366,6 +381,27 @@ def parse_events(props):
366381
return events
367382

368383

384+
def parse_wildcards(props):
385+
"""
386+
Pull out the wildcard attributes from the Component props
387+
388+
Parameters
389+
----------
390+
props: dict
391+
Dictionary with {propName: propMetadata} structure
392+
393+
Returns
394+
-------
395+
list
396+
List of Dash valid wildcard prefixes
397+
"""
398+
list_of_valid_wildcard_attr_prefixes = []
399+
for wildcard_attr in ["data-*"]:
400+
if wildcard_attr in props.keys():
401+
list_of_valid_wildcard_attr_prefixes.append(wildcard_attr[:-1])
402+
return list_of_valid_wildcard_attr_prefixes
403+
404+
369405
def reorder_props(props):
370406
"""
371407
If "children" is in props, then move it to the

tests/development/metadata_test.json

+7
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@
207207
"required": false,
208208
"description": ""
209209
},
210+
"data-*": {
211+
"type": {
212+
"name": "string"
213+
},
214+
"required": false,
215+
"description": ""
216+
},
210217
"id": {
211218
"type": {
212219
"name": "string"

tests/development/test_base_component.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Component._prop_names = ('id', 'a', 'children', 'style', )
1818
Component._type = 'TestComponent'
1919
Component._namespace = 'test_namespace'
20+
Component._valid_wildcard_attr_names = ['data-']
2021

2122

2223
def nested_tree():
@@ -195,10 +196,10 @@ def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_list
195196
None,
196197
'wrap string',
197198
{
198-
'type': 'TestComponent',
199+
'type': 'TestComponent', # noqa:E501
199200
'namespace': 'test_namespace', # noqa: E501
200201
'props': {
201-
'children': 'string',
202+
'children': 'string', # noqa:E501
202203
'id': '0.1.x.x.0'
203204
}
204205
},
@@ -467,6 +468,10 @@ def test_pop(self):
467468
self.assertTrue('2' not in c)
468469
self.assertTrue(c2_popped is c2)
469470

471+
def test_data_wildcard(self):
472+
c = Component(id='1', **{'data-one': '1'})
473+
self.assertEqual(getattr(c, 'data-one'), '1')
474+
470475

471476
class TestGenerateClass(unittest.TestCase):
472477
def setUp(self):
@@ -580,6 +585,13 @@ def test_repr_nested_arguments(self):
580585
"Table(Table(children=Table(id='1'), id='2'))"
581586
)
582587

588+
def test_repr_wildcard_arguments(self):
589+
c = self.ComponentClass(id='1', **{'data-one': "one"})
590+
self.assertEqual(
591+
repr(c),
592+
"Table(id='1', data-one='one')"
593+
)
594+
583595
def test_docstring(self):
584596
assert_docstring(self.assertEqual, self.ComponentClass.__doc__)
585597

@@ -674,6 +686,8 @@ def setUp(self):
674686

675687
['customArrayProp', 'list'],
676688

689+
['data-*', 'string'],
690+
677691
['id', 'string'],
678692

679693
['dashEvents', "a value equal to: 'restyle', 'relayout', 'click'"]
@@ -746,9 +760,9 @@ def assert_docstring(assertEqual, docstring):
746760

747761
"- optionalAny (boolean | number | string | dict | "
748762
"list; optional)",
749-
750763
"- customProp (optional)",
751764
"- customArrayProp (list; optional)",
765+
"- data-* (string; optional)",
752766
'- id (string; optional)',
753767
'',
754768
"Available events: 'restyle', 'relayout', 'click'",

tests/development/test_component_loader.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
"description": "Children",
2929
"required": false
3030
},
31+
"data-*": {
32+
"type": {
33+
"name": "string"
34+
},
35+
"required": false,
36+
"description": "Wildcard data attribute"
37+
},
3138
"bar": {
3239
"type": {
3340
"name": "custom"
@@ -69,6 +76,13 @@
6976
"required": false,
7077
"description": "The URL of a linked resource."
7178
},
79+
"data-*": {
80+
"type": {
81+
"name": "string"
82+
},
83+
"required": false,
84+
"description": "Wildcard data attribute"
85+
},
7286
"children": {
7387
"type": {
7488
"name": "object"
@@ -113,11 +127,13 @@ def test_loadcomponents(self):
113127
'foo': 'Hello World',
114128
'bar': 'Lah Lah',
115129
'baz': 'Lemons',
130+
'data-blah': 'apples',
116131
'children': 'Child'
117132
}
118133
AKwargs = {
119134
'children': 'Child',
120-
'href': 'Hello World'
135+
'href': 'Hello World',
136+
'data-a': 'bananas'
121137
}
122138

123139
self.assertTrue(

tox.ini

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ basepython={env:TOX_PYTHON_27}
1010
commands =
1111
python --version
1212
python -m unittest tests.test_integration
13-
python -m unittest tests.test_react
1413
python -m unittest tests.test_resources
15-
14+
python -m unittest tests.development.test_base_component
15+
python -m unittest tests.development.test_component_loader
1616
flake8 dash setup.py
1717
pylint dash setup.py
1818

@@ -21,7 +21,8 @@ basepython={env:TOX_PYTHON_36}
2121
commands =
2222
python --version
2323
python -m unittest tests.test_integration
24-
python -m unittest tests.test_react
2524
python -m unittest tests.test_resources
25+
python -m unittest tests.development.test_base_component
26+
python -m unittest tests.development.test_component_loader
2627
flake8 dash setup.py
2728
pylint dash setup.py

0 commit comments

Comments
 (0)