1
1
import plotly .io as pio
2
2
import plotly .io .kaleido
3
- import sys
4
3
from contextlib import contextmanager
5
-
6
- if sys .version_info >= (3 , 3 ):
7
- from unittest .mock import Mock
8
- else :
9
- from mock import Mock
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from unittest .mock import Mock
10
7
11
8
fig = {"layout" : {"title" : {"text" : "figure title" }}}
12
9
13
10
11
+ def make_writeable_mocks ():
12
+ """Produce some mocks which we will use for testing the `write_image()` function.
13
+
14
+ These mocks should be passed as the `file=` argument to `write_image()`.
15
+
16
+ The tests should verify that the method specified in the `active_write_function`
17
+ attribute is called once, and that scope.transform is called with the `format=`
18
+ argument specified by the `.expected_format` attribute.
19
+
20
+ In total we provide two mocks: one for a writable file descriptor, and other for a
21
+ pathlib.Path object.
22
+ """
23
+
24
+ # Part 1: A mock for a file descriptor
25
+ # ------------------------------------
26
+ mock_file_descriptor = Mock ()
27
+
28
+ # A file descriptor has no write_bytes method, unlike a pathlib Path.
29
+ del mock_file_descriptor .write_bytes
30
+
31
+ # The expected write method for a file descriptor is .write
32
+ mock_file_descriptor .active_write_function = mock_file_descriptor .write
33
+
34
+ # Since there is no filename, there should be no format detected.
35
+ mock_file_descriptor .expected_format = None
36
+
37
+ # Part 2: A mock for a pathlib path
38
+ # ---------------------------------
39
+ mock_pathlib_path = Mock (spec = Path )
40
+
41
+ # A pathlib Path object has no write method, unlike a file descriptor.
42
+ del mock_pathlib_path .write
43
+
44
+ # The expected write method for a pathlib Path is .write_bytes
45
+ mock_pathlib_path .active_write_function = mock_pathlib_path .write_bytes
46
+
47
+ # Mock a path with PNG suffix
48
+ mock_pathlib_path .suffix = ".png"
49
+ mock_pathlib_path .expected_format = "png"
50
+
51
+ return mock_file_descriptor , mock_pathlib_path
52
+
53
+
14
54
@contextmanager
15
55
def mocked_scope ():
16
56
# Code to acquire resource, e.g.:
@@ -44,15 +84,19 @@ def test_kaleido_engine_to_image():
44
84
45
85
46
86
def test_kaleido_engine_write_image ():
47
- writeable_mock = Mock ()
48
- with mocked_scope () as scope :
49
- pio .write_image (fig , writeable_mock , engine = "kaleido" , validate = False )
87
+ for writeable_mock in make_writeable_mocks ():
88
+ with mocked_scope () as scope :
89
+ pio .write_image (fig , writeable_mock , engine = "kaleido" , validate = False )
50
90
51
- scope .transform .assert_called_with (
52
- fig , format = None , width = None , height = None , scale = None
53
- )
91
+ scope .transform .assert_called_with (
92
+ fig ,
93
+ format = writeable_mock .expected_format ,
94
+ width = None ,
95
+ height = None ,
96
+ scale = None ,
97
+ )
54
98
55
- assert writeable_mock .write .call_count == 1
99
+ assert writeable_mock .active_write_function .call_count == 1
56
100
57
101
58
102
def test_kaleido_engine_to_image_kwargs ():
@@ -73,24 +117,24 @@ def test_kaleido_engine_to_image_kwargs():
73
117
74
118
75
119
def test_kaleido_engine_write_image_kwargs ():
76
- writeable_mock = Mock ()
77
- with mocked_scope () as scope :
78
- pio .write_image (
79
- fig ,
80
- writeable_mock ,
81
- format = "jpg" ,
82
- width = 700 ,
83
- height = 600 ,
84
- scale = 2 ,
85
- engine = "kaleido" ,
86
- validate = False ,
120
+ for writeable_mock in make_writeable_mocks ():
121
+ with mocked_scope () as scope :
122
+ pio .write_image (
123
+ fig ,
124
+ writeable_mock ,
125
+ format = "jpg" ,
126
+ width = 700 ,
127
+ height = 600 ,
128
+ scale = 2 ,
129
+ engine = "kaleido" ,
130
+ validate = False ,
131
+ )
132
+
133
+ scope .transform .assert_called_with (
134
+ fig , format = "jpg" , width = 700 , height = 600 , scale = 2
87
135
)
88
136
89
- scope .transform .assert_called_with (
90
- fig , format = "jpg" , width = 700 , height = 600 , scale = 2
91
- )
92
-
93
- assert writeable_mock .write .call_count == 1
137
+ assert writeable_mock .active_write_function .call_count == 1
94
138
95
139
96
140
def test_image_renderer ():
@@ -105,3 +149,17 @@ def test_image_renderer():
105
149
height = renderer .height ,
106
150
scale = renderer .scale ,
107
151
)
152
+
153
+
154
+ def test_bytesio ():
155
+ """Verify that writing to a BytesIO object contains the same data as to_image().
156
+
157
+ The goal of this test is to ensure that Plotly correctly handles a writable buffer
158
+ which doesn't correspond to a filesystem path.
159
+ """
160
+ bio = BytesIO ()
161
+ pio .write_image (fig , bio , format = "jpg" , engine = "kaleido" , validate = False )
162
+ bio .seek (0 ) # Rewind to the beginning of the buffer, otherwise read() returns b''.
163
+ bio_bytes = bio .read ()
164
+ to_image_bytes = pio .to_image (fig , format = "jpg" , engine = "kaleido" , validate = False )
165
+ assert bio_bytes == to_image_bytes
0 commit comments