From 0d32a55c5f6e46edcf8b8d2b0a61349629c14049 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Jan 2019 16:06:48 -0600 Subject: [PATCH 1/5] Fixed handling of characters that have no uppercase --- holoviews/core/tree.py | 8 +++++--- holoviews/core/util.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/holoviews/core/tree.py b/holoviews/core/tree.py index 27568567c3..5464e74c0e 100644 --- a/holoviews/core/tree.py +++ b/holoviews/core/tree.py @@ -218,12 +218,13 @@ def __delitem__(self, identifier): def __setattr__(self, identifier, val): # Getattr is skipped for root and first set of children shallow = (self.parent is None or self.parent.parent is None) - if identifier[0].isupper() and self.fixed and shallow: + + if util.tree_attribute(identifier) and self.fixed and shallow: raise AttributeError(self._fixed_error % identifier) super(AttrTree, self).__setattr__(identifier, val) - if identifier[0].isupper(): + if util.tree_attribute(identifier): if not identifier in self.children: self.children.append(identifier) self._propagate((identifier,), val) @@ -254,7 +255,8 @@ def __getattr__(self, identifier): if sanitized in self.children: return self.__dict__[sanitized] - if not sanitized.startswith('_') and identifier[0].isupper(): + + if not sanitized.startswith('_') and util.tree_attribute(identifier): self.children.append(sanitized) dir_mode = self.__dict__['_dir_mode'] child_tree = self.__class__(identifier=sanitized, diff --git a/holoviews/core/util.py b/holoviews/core/util.py index f41b134cbb..ee45e53f56 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -338,6 +338,19 @@ def deephash(obj): return None +def tree_attribute(identifier): + """ + Predicate that returns True for custom attributes added to AttrTrees + that are not methods, properties or internal attributes. + + These custom attributes start with a capitalized character when + applicable (not applicable to underscore or certain unicode characters) + """ + if identifier[0].upper().isupper() is False and identifier[0] != '_': + return True + else: + return identifier[0].isupper() + def argspec(callable_obj): """ Returns an ArgSpec object for functions, staticmethods, instance From 79eedd3da9bb7671e6ac23242c4aa3148c840f04 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Jan 2019 17:09:53 -0600 Subject: [PATCH 2/5] Added unit test to testlayouts.py --- holoviews/tests/core/testlayouts.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/holoviews/tests/core/testlayouts.py b/holoviews/tests/core/testlayouts.py index ac57222715..0182f97e8d 100644 --- a/holoviews/tests/core/testlayouts.py +++ b/holoviews/tests/core/testlayouts.py @@ -1,7 +1,8 @@ +import sys from holoviews import AdjointLayout, NdLayout, GridSpace, Layout, Element, HoloMap, Overlay -from holoviews.element import HLine +from holoviews.element import HLine, Curve from holoviews.element.comparison import ComparisonTestCase - +from unittest import SkipTest class CompositeTest(ComparisonTestCase): "For testing of basic composite element types" @@ -19,6 +20,13 @@ def setUp(self): def test_add_operator(self): self.assertEqual(type(self.view1 + self.view2), Layout) + def test_add_unicode_py3(self): + "Test to avoid regression of #3403 where unicode characters don't capitalize" + if sys.version_info.major == 2: raise SkipTest + layout = Curve([-1,-2,-3]) + Curve([1,2,3]) .relabel('𝜗_1 vs th_2') + elements = list(layout) + self.assertEqual(len(elements), 2) + class AdjointLayoutTest(CompositeTest): From b8ad8c2bfd8407c46aa98267a0ddcc4bb3829aad Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Jan 2019 17:16:45 -0600 Subject: [PATCH 3/5] Added four unit tests of tree_attribute utility --- holoviews/tests/core/testutils.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/holoviews/tests/core/testutils.py b/holoviews/tests/core/testutils.py index 097a1dceb3..8153806989 100644 --- a/holoviews/tests/core/testutils.py +++ b/holoviews/tests/core/testutils.py @@ -19,7 +19,7 @@ sanitize_identifier_fn, find_range, max_range, wrap_tuple_streams, deephash, merge_dimensions, get_path, make_path_unique, compute_density, date_range, dt_to_int, compute_edges, isfinite, cross_index, closest_match, - dimension_range + dimension_range, tree_attribute ) from holoviews import Dimension, Element from holoviews.streams import PointerXY @@ -191,6 +191,23 @@ def test_prefix_test3_py3(self): self.assertEqual(prefixed, True) +class TestTreeAttribute(ComparisonTestCase): + + def test_simple_lowercase_string(self): + self.assertEqual(tree_attribute('lowercase'), False) + + def test_simple_uppercase_string(self): + self.assertEqual(tree_attribute('UPPERCASE'), True) + + def test_unicode_string(self): + if py_version != 2: raise SkipTest + self.assertEqual(tree_attribute('𝜗unicode'), True) + + def test_underscore_string(self): + if py_version != 2: raise SkipTest + self.assertEqual(tree_attribute('_underscore'), True) + + class TestSanitizationPy2(ComparisonTestCase): """ Tests of sanitize_identifier (Python 2) From 087737f05d13f24655515b8fba8332611c3adc1b Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Jan 2019 18:00:50 -0600 Subject: [PATCH 4/5] Declared encoding in testlayouts.py --- holoviews/tests/core/testlayouts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/holoviews/tests/core/testlayouts.py b/holoviews/tests/core/testlayouts.py index 0182f97e8d..3a26071035 100644 --- a/holoviews/tests/core/testlayouts.py +++ b/holoviews/tests/core/testlayouts.py @@ -1,3 +1,8 @@ +# -*- coding: utf-8 -*- +""" +Tests of Layout and related classes +""" + import sys from holoviews import AdjointLayout, NdLayout, GridSpace, Layout, Element, HoloMap, Overlay from holoviews.element import HLine, Curve From 8a2519b3ee105b5a4b047b101109489fe75ddca3 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Jan 2019 18:35:51 -0600 Subject: [PATCH 5/5] Fixed incorrect unit test definition --- holoviews/tests/core/testutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/holoviews/tests/core/testutils.py b/holoviews/tests/core/testutils.py index 8153806989..b1e2b37995 100644 --- a/holoviews/tests/core/testutils.py +++ b/holoviews/tests/core/testutils.py @@ -204,8 +204,7 @@ def test_unicode_string(self): self.assertEqual(tree_attribute('𝜗unicode'), True) def test_underscore_string(self): - if py_version != 2: raise SkipTest - self.assertEqual(tree_attribute('_underscore'), True) + self.assertEqual(tree_attribute('_underscore'), False) class TestSanitizationPy2(ComparisonTestCase):