Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Props blacklist #753

Merged
merged 8 commits into from
May 30, 2019
4 changes: 2 additions & 2 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ def _value_is_valid(val):
def _validate_value(val, index=None):
# val is a Component
if isinstance(val, Component):
for p, j in val.traverse_with_paths():
for p, j in val._traverse_with_paths():
# check each component value in the tree
if not _value_is_valid(j):
_raise_invalid(
Expand Down Expand Up @@ -1194,7 +1194,7 @@ def _validate_layout(self):
layout_id = getattr(self.layout, 'id', None)

component_ids = {layout_id} if layout_id else set()
for component in to_validate.traverse():
for component in to_validate._traverse():
component_id = getattr(component, 'id', None)
if component_id and component_id in component_ids:
raise exceptions.DuplicateIdError(
Expand Down
16 changes: 8 additions & 8 deletions dash/development/base_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _check_if_has_indexable_children(item):


@six.add_metaclass(ComponentMeta)
class Component(patch_collections_abc('MutableMapping')):
class Component(object):
class _UNDEFINED(object):
def __repr__(self):
return 'undefined'
Expand Down Expand Up @@ -184,7 +184,7 @@ def _get_set_or_delete(self, id, operation, new_item=None):
# If we were in a list, then this exception will get caught
raise KeyError(id)

# Supply ABC methods for a MutableMapping:
# Magic methods for a mapping interface:
# - __getitem__
# - __setitem__
# - __delitem__
Expand All @@ -208,12 +208,12 @@ def __delitem__(self, id): # pylint: disable=redefined-builtin
"""Delete items by ID in the tree of children."""
return self._get_set_or_delete(id, 'delete')

def traverse(self):
def _traverse(self):
"""Yield each item in the tree."""
for t in self.traverse_with_paths():
for t in self._traverse_with_paths():
yield t[1]

def traverse_with_paths(self):
def _traverse_with_paths(self):
"""Yield each item with its path in the tree."""
children = getattr(self, 'children', None)
children_type = type(children).__name__
Expand All @@ -224,7 +224,7 @@ def traverse_with_paths(self):
# children is just a component
if isinstance(children, Component):
yield "[*] " + children_string, children
for p, t in children.traverse_with_paths():
for p, t in children._traverse_with_paths():
yield "\n".join(["[*] " + children_string, p]), t

# children is a list of components
Expand All @@ -238,12 +238,12 @@ def traverse_with_paths(self):
yield list_path, i

if isinstance(i, Component):
for p, t in i.traverse_with_paths():
for p, t in i._traverse_with_paths():
yield "\n".join([list_path, p]), t

def __iter__(self):
"""Yield IDs in the tree of children."""
for t in self.traverse():
for t in self._traverse():
if (isinstance(t, Component) and
getattr(t, 'id', None) is not None):

Expand Down
107 changes: 45 additions & 62 deletions tests/unit/dash/development/test_base_component.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections import OrderedDict
import collections
import inspect
import json
import os
Expand All @@ -8,8 +7,16 @@
import plotly

from dash.development.base_component import Component
from dash.development._py_components_generation import generate_class_string, generate_class_file, generate_class, \
create_docstring, prohibit_events, js_to_py_type
from dash.development._py_components_generation import (
generate_class_string,
generate_class_file,
generate_class,
create_docstring,
prohibit_events,
js_to_py_type
)

_dir = os.path.dirname(os.path.abspath(__file__))

Component._prop_names = ('id', 'a', 'children', 'style', )
Component._type = 'TestComponent'
Expand Down Expand Up @@ -85,8 +92,9 @@ def test_get_item_with_nested_children_two_branches(self):

def test_get_item_with_nested_children_with_mixed_strings_and_without_lists(self): # noqa: E501
c, c1, c2, c3, c4, c5 = nested_tree()
keys = [k for k in c]
self.assertEqual(
list(c.keys()),
keys,
[
'0.0',
'0.1',
Expand Down Expand Up @@ -135,15 +143,16 @@ def test_set_item_with_nested_children_with_mixed_strings_and_without_lists(self

def test_del_item_with_nested_children_with_mixed_strings_and_without_lists(self): # noqa: E501
c = nested_tree()[0]
for key in reversed(list(c.keys())):
keys = reversed([k for k in c])
for key in keys:
c[key]
del c[key]
with self.assertRaises(KeyError):
c[key]

def test_traverse_with_nested_children_with_mixed_strings_and_without_lists(self): # noqa: E501
c, c1, c2, c3, c4, c5 = nested_tree()
elements = [i for i in c.traverse()]
elements = [i for i in c._traverse()]
self.assertEqual(
elements,
c.children + [c3] + [c2] + c2.children
Expand All @@ -153,19 +162,12 @@ def test_traverse_with_tuples(self): # noqa: E501
c, c1, c2, c3, c4, c5 = nested_tree()
c2.children = tuple(c2.children)
c.children = tuple(c.children)
elements = [i for i in c.traverse()]
elements = [i for i in c._traverse()]
self.assertEqual(
elements,
list(c.children) + [c3] + [c2] + list(c2.children)
)

def test_iter_with_nested_children_with_mixed_strings_and_without_lists(self): # noqa: E501
c = nested_tree()[0]
keys = list(c.keys())
# get a list of ids that __iter__ provides
iter_keys = [i for i in c]
self.assertEqual(keys, iter_keys)

def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_lists(self): # noqa: E501
c = nested_tree()[0]
Component._namespace
Expand Down Expand Up @@ -244,17 +246,6 @@ def test_get_item_raises_key_if_id_doesnt_exist(self):
with self.assertRaises(KeyError):
c3['0']

def test_equality(self):
# TODO - Why is this the case? How is == being performed?
# __eq__ only needs __getitem__, __iter__, and __len__
self.assertTrue(Component() == Component())
self.assertTrue(Component() is not Component())

c1 = Component(id='1')
c2 = Component(id='2', children=[Component()])
self.assertTrue(c1 == c2)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of a nice side effect, IMO, that components are now no longer always equal to each other - though I don't know why this would come up...

self.assertTrue(c1 is not c2)

def test_set_item(self):
c1a = Component(id='1', children='Hello world')
c2 = Component(id='2', children=c1a)
Expand Down Expand Up @@ -450,6 +441,9 @@ def test_len(self):
])), 3)

def test_iter(self):
# The mixin methods from MutableMapping were cute but probably never
# used - at least not by us. Test that they're gone

# keys, __contains__, items, values, and more are all mixin methods
# that we get for free by inheriting from the MutableMapping
# and behave as according to our implementation of __iter__
Expand All @@ -469,40 +463,37 @@ def test_iter(self):
Component(children=[Component(id='8')]),
]
)
# test keys()
keys = [k for k in list(c.keys())]
self.assertEqual(keys, ['2', '3', '4', '5', '6', '7', '8'])
self.assertEqual([i for i in c], keys)

# test values()
components = [i for i in list(c.values())]
self.assertEqual(components, [c[k] for k in keys])
mixins = ['clear', 'get', 'items', 'keys', 'pop', 'popitem',
'setdefault', 'update', 'values']

for m in mixins:
self.assertFalse(hasattr(c, m), m)

keys = ['2', '3', '4', '5', '6', '7', '8']

# test __iter__()
for k in keys:
# test __contains__()
self.assertTrue(k in c)
# test __getitem__()
self.assertEqual(c[k].id, k)

# test __items__
items = [i for i in list(c.items())]
self.assertEqual(list(zip(keys, components)), items)
# test __iter__()
keys2 = []
for k in c:
keys2.append(k)
self.assertIn(k, keys)

def test_pop(self):
c2 = Component(id='2')
c = Component(id='1', children=c2)
c2_popped = c.pop('2')
self.assertTrue('2' not in c)
self.assertTrue(c2_popped is c2)
self.assertEqual(len(keys), len(keys2))


class TestGenerateClassFile(unittest.TestCase):
def setUp(self):
json_path = os.path.join(
'tests', 'unit', 'dash', 'development', 'metadata_test.json')
json_path = os.path.join(_dir, 'metadata_test.json')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that you can run the tests from any working directory, not just the repo root.

with open(json_path) as data_file:
json_string = data_file.read()
data = json\
.JSONDecoder(object_pairs_hook=collections.OrderedDict)\
.JSONDecoder(object_pairs_hook=OrderedDict)\
.decode(json_string)
self.data = data

Expand Down Expand Up @@ -537,9 +528,7 @@ def setUp(self):
self.written_class_string = f.read()

# The expected result for both class string and class file generation
expected_string_path = os.path.join(
'tests', 'unit', 'dash', 'development', 'metadata_test.py'
)
expected_string_path = os.path.join(_dir, 'metadata_test.py')
with open(expected_string_path, 'r') as f:
self.expected_class_string = f.read()

Expand Down Expand Up @@ -567,12 +556,11 @@ def test_class_file(self):

class TestGenerateClass(unittest.TestCase):
def setUp(self):
path = os.path.join(
'tests', 'unit', 'dash', 'development', 'metadata_test.json')
path = os.path.join(_dir, 'metadata_test.json')
with open(path) as data_file:
json_string = data_file.read()
data = json\
.JSONDecoder(object_pairs_hook=collections.OrderedDict)\
.JSONDecoder(object_pairs_hook=OrderedDict)\
.decode(json_string)
self.data = data

Expand All @@ -583,14 +571,11 @@ def setUp(self):
namespace='TableComponents'
)

path = os.path.join(
'tests', 'unit', 'dash', 'development',
'metadata_required_test.json'
)
path = os.path.join(_dir, 'metadata_required_test.json')
with open(path) as data_file:
json_string = data_file.read()
required_data = json\
.JSONDecoder(object_pairs_hook=collections.OrderedDict)\
.JSONDecoder(object_pairs_hook=OrderedDict)\
.decode(json_string)
self.required_data = required_data

Expand Down Expand Up @@ -756,12 +741,11 @@ def test_required_props(self):

class TestMetaDataConversions(unittest.TestCase):
def setUp(self):
path = os.path.join(
'tests', 'unit', 'dash', 'development', 'metadata_test.json')
path = os.path.join(_dir, 'metadata_test.json')
with open(path) as data_file:
json_string = data_file.read()
data = json\
.JSONDecoder(object_pairs_hook=collections.OrderedDict)\
.JSONDecoder(object_pairs_hook=OrderedDict)\
.decode(json_string)
self.data = data

Expand Down Expand Up @@ -940,12 +924,11 @@ def assert_docstring(assertEqual, docstring):

class TestFlowMetaDataConversions(unittest.TestCase):
def setUp(self):
path = os.path.join(
'tests', 'unit', 'dash', 'development', 'flow_metadata_test.json')
path = os.path.join(_dir, 'flow_metadata_test.json')
with open(path) as data_file:
json_string = data_file.read()
data = json\
.JSONDecoder(object_pairs_hook=collections.OrderedDict)\
.JSONDecoder(object_pairs_hook=OrderedDict)\
.decode(json_string)
self.data = data

Expand Down