1#-----------------------------------------------------------------------------
2# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors.
3# All rights reserved.
4#
5# The full license is in the file LICENSE.txt, distributed with this software.
6#-----------------------------------------------------------------------------
7
8#-----------------------------------------------------------------------------
9# Boilerplate
10#-----------------------------------------------------------------------------
11import pytest ; pytest
12
13#-----------------------------------------------------------------------------
14# Imports
15#-----------------------------------------------------------------------------
16
17# External imports
18import numpy as np
19from mock import patch
20
21# Bokeh imports
22from bokeh._testing.util.api import verify_all
23from bokeh.core.has_props import HasProps
24
25# Module under test
26import bokeh.core.property.bases as bcpb # isort:skip
27
28#-----------------------------------------------------------------------------
29# Setup
30#-----------------------------------------------------------------------------
31
32ALL = (
33    'ContainerProperty',
34    'DeserializationError',
35    'PrimitiveProperty',
36    'Property',
37    'validation_on',
38)
39
40#-----------------------------------------------------------------------------
41# General API
42#-----------------------------------------------------------------------------
43
44class TestProperty:
45    @patch('bokeh.core.property.bases.Property.validate')
46    def test_is_valid_supresses_validation_detail(self, mock_validate) -> None:
47        p = bcpb.Property()
48        p.is_valid(None)
49        assert mock_validate.called
50        assert mock_validate.call_args[0] == (None, False)
51
52    def test_serialized_default(self) -> None:
53        p = bcpb.Property()
54        assert p.serialized == True
55        assert p.readonly == False
56
57        # readonly=True sets serialized=False if unspecified
58        p = bcpb.Property(readonly=True)
59        assert p.serialized == False
60        assert p.readonly == True
61
62        # explicit serialized value always respected
63        p = bcpb.Property(readonly=True, serialized=True)
64        assert p.serialized == True
65        assert p.readonly == True
66
67
68    def test_assert_bools(self) -> None:
69        hp = HasProps()
70        p = bcpb.Property()
71
72        p.asserts(True, "true")
73        assert p.prepare_value(hp, "foo", 10) == 10
74
75        p.asserts(False, "false")
76        with pytest.raises(ValueError) as e:
77                p.prepare_value(hp, "foo", 10)
78                assert str(e) == "false"
79
80    def test_assert_functions(self) -> None:
81        hp = HasProps()
82        p = bcpb.Property()
83
84        p.asserts(lambda obj, value: True, "true")
85        p.asserts(lambda obj, value: obj is hp, "true")
86        p.asserts(lambda obj, value: value==10, "true")
87        assert p.prepare_value(hp, "foo", 10) == 10
88
89        p.asserts(lambda obj, value: False, "false")
90        with pytest.raises(ValueError) as e:
91                p.prepare_value(hp, "foo", 10)
92                assert str(e) == "false"
93
94    def test_assert_msg_funcs(self) -> None:
95        hp = HasProps()
96        p = bcpb.Property()
97
98        def raise_(ex):
99                raise ex
100
101        p.asserts(False, lambda obj, name, value: raise_(ValueError("bad %s %s %s" % (hp==obj, name, value))))
102
103        with pytest.raises(ValueError) as e:
104                p.prepare_value(hp, "foo", 10)
105                assert str(e) == "bad True name, 10"
106
107    def test_matches_basic_types(self, capsys) -> None:
108        p = bcpb.Property()
109        for x in [1, 1.2, "a", np.arange(4), None, False, True, {}, []]:
110                assert p.matches(x, x) is True
111                assert p.matches(x, "junk") is False
112        out, err = capsys.readouterr()
113        assert err == ""
114
115    def test_matches_compatible_arrays(self, capsys) -> None:
116        p = bcpb.Property()
117        a = np.arange(5)
118        b = np.arange(5)
119        assert p.matches(a, b) is True
120        assert p.matches(a, b+1) is False
121        for x in [1, 1.2, "a", np.arange(4), None, False]:
122                assert p.matches(a, x) is False
123                assert p.matches(x, b) is False
124        out, err = capsys.readouterr()
125        assert err == ""
126
127    def test_matches_incompatible_arrays(self, capsys) -> None:
128        p = bcpb.Property()
129        a = np.arange(5)
130        b = np.arange(5).astype(str)
131        assert p.matches(a, b) is False
132        out, err = capsys.readouterr()
133        # no way to suppress FutureWarning in this case
134        # assert err == ""
135
136    def test_matches_dicts_with_array_values(self, capsys) -> None:
137        p = bcpb.Property()
138        d1 = dict(foo=np.arange(10))
139        d2 = dict(foo=np.arange(10))
140
141        assert p.matches(d1, d1) is True
142        assert p.matches(d1, d2) is True
143
144        # XXX not sure if this is preferable to have match, or not
145        assert p.matches(d1, dict(foo=list(range(10)))) is True
146
147        assert p.matches(d1, dict(foo=np.arange(11))) is False
148        assert p.matches(d1, dict(bar=np.arange(10))) is False
149        assert p.matches(d1, dict(bar=10)) is False
150        out, err = capsys.readouterr()
151        assert err == ""
152
153    def test_matches_non_dict_containers_with_array_false(self, capsys) -> None:
154        p = bcpb.Property()
155        d1 = [np.arange(10)]
156        d2 = [np.arange(10)]
157        assert p.matches(d1, d1) is True  # because object identity
158        assert p.matches(d1, d2) is False
159
160        t1 = (np.arange(10),)
161        t2 = (np.arange(10),)
162        assert p.matches(t1, t1) is True  # because object identity
163        assert p.matches(t1, t2) is False
164
165        out, err = capsys.readouterr()
166        assert err == ""
167
168    def test_matches_dicts_with_series_values(self, capsys, pd) -> None:
169        p = bcpb.Property()
170        d1 = pd.DataFrame(dict(foo=np.arange(10)))
171        d2 = pd.DataFrame(dict(foo=np.arange(10)))
172
173        assert p.matches(d1.foo, d1.foo) is True
174        assert p.matches(d1.foo, d2.foo) is True
175
176        # XXX not sure if this is preferable to have match, or not
177        assert p.matches(d1.foo, (range(10))) is True
178
179        assert p.matches(d1.foo, np.arange(11)) is False
180        assert p.matches(d1.foo, np.arange(10)+1) is False
181        assert p.matches(d1.foo, 10) is False
182        out, err = capsys.readouterr()
183        assert err == ""
184
185    def test_matches_dicts_with_index_values(self, capsys, pd) -> None:
186        p = bcpb.Property()
187        d1 = pd.DataFrame(dict(foo=np.arange(10)))
188        d2 = pd.DataFrame(dict(foo=np.arange(10)))
189
190        assert p.matches(d1.index, d1.index) is True
191        assert p.matches(d1.index, d2.index) is True
192
193        # XXX not sure if this is preferable to have match, or not
194        assert p.matches(d1.index, list(range(10))) is True
195
196        assert p.matches(d1.index, np.arange(11)) is False
197        assert p.matches(d1.index, np.arange(10)+1) is False
198        assert p.matches(d1.index, 10) is False
199        out, err = capsys.readouterr()
200        assert err == ""
201
202    def test_validation_on(self) -> None:
203        assert bcpb.Property._should_validate == True
204        assert bcpb.validation_on()
205
206        bcpb.Property._should_validate = False
207        assert not bcpb.validation_on()
208
209        bcpb.Property._should_validate = True
210        assert bcpb.validation_on()
211
212#-----------------------------------------------------------------------------
213# Dev API
214#-----------------------------------------------------------------------------
215
216#-----------------------------------------------------------------------------
217# Private API
218#-----------------------------------------------------------------------------
219
220#-----------------------------------------------------------------------------
221# Code
222#-----------------------------------------------------------------------------
223
224Test___all__ = verify_all(bcpb, ALL)
225