Skip to content

Commit 8dfe76a

Browse files
authored
Merge pull request #126 from highcharts-for-python/develop
PR for v.1.4.3
2 parents cfbcf5e + 1c0eb74 commit 8dfe76a

File tree

4 files changed

+221
-15
lines changed

4 files changed

+221
-15
lines changed

CHANGES.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11

2+
Release 1.4.3
3+
=========================================
4+
5+
* **BUGFIX:** Fixed edge case error when deserializing ``ChartOptions`` using ``.from_dict()``
6+
with a ``dict`` that had been serialized using ``.to_dict()`` which errored on ``.margin``
7+
and ``.spacing`` (#124).
8+
9+
--------------------
10+
211
Release 1.4.2
312
=========================================
413

highcharts_core/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.2'
1+
__version__ = '1.4.3'

highcharts_core/options/chart/__init__.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -592,10 +592,22 @@ def margin(self, value):
592592
f'or an iterable of four values. '
593593
f'Received an iterable of {len(value)} '
594594
f'values ({value})')
595-
self.margin_top = value[0]
596-
self.margin_right = value[1]
597-
self.margin_bottom = value[2]
598-
self.margin_left = value[3]
595+
if value[0] == 'null':
596+
self.margin_top = None
597+
else:
598+
self.margin_top = value[0]
599+
if value[1] == 'null':
600+
self.margin_right = None
601+
else:
602+
self.margin_right = value[1]
603+
if value[2] == 'null':
604+
self.margin_bottom = None
605+
else:
606+
self.margin_bottom = value[2]
607+
if value[3] == 'null':
608+
self.margin_left = None
609+
else:
610+
self.margin_left = value[3]
599611
else:
600612
self.margin_top = value
601613
self.margin_right = value
@@ -620,7 +632,10 @@ def margin_bottom(self) -> Optional[int | float | Decimal]:
620632

621633
@margin_bottom.setter
622634
def margin_bottom(self, value):
623-
self._margin_bottom = validators.numeric(value, allow_empty = True)
635+
if value is None or isinstance(value, constants.EnforcedNullType):
636+
self._margin_bottom = None
637+
else:
638+
self._margin_bottom = validators.numeric(value)
624639

625640
@property
626641
def margin_left(self) -> Optional[int | float | Decimal]:
@@ -640,7 +655,10 @@ def margin_left(self) -> Optional[int | float | Decimal]:
640655

641656
@margin_left.setter
642657
def margin_left(self, value):
643-
self._margin_left = validators.numeric(value, allow_empty = True)
658+
if value is None or isinstance(value, constants.EnforcedNullType):
659+
self._margin_left = None
660+
else:
661+
self._margin_left = validators.numeric(value)
644662

645663
@property
646664
def margin_right(self) -> Optional[int | float | Decimal]:
@@ -660,7 +678,10 @@ def margin_right(self) -> Optional[int | float | Decimal]:
660678

661679
@margin_right.setter
662680
def margin_right(self, value):
663-
self._margin_right = validators.numeric(value, allow_empty = True)
681+
if value is None or isinstance(value, constants.EnforcedNullType):
682+
self._margin_right = None
683+
else:
684+
self._margin_right = validators.numeric(value)
664685

665686
@property
666687
def margin_top(self) -> Optional[int | float | Decimal]:
@@ -680,7 +701,10 @@ def margin_top(self) -> Optional[int | float | Decimal]:
680701

681702
@margin_top.setter
682703
def margin_top(self, value):
683-
self._margin_top = validators.numeric(value, allow_empty = True)
704+
if value is None or isinstance(value, constants.EnforcedNullType):
705+
self._margin_top = None
706+
else:
707+
self._margin_top = validators.numeric(value)
684708

685709
@property
686710
def number_formatter(self) -> Optional[CallbackFunction]:
@@ -1119,13 +1143,23 @@ def spacing(self, value):
11191143
f' or an iterable of four values. '
11201144
f'Received an iterable of {len(value)} '
11211145
f'values ({value})')
1122-
value = [validators.numeric(x) for x in value]
1123-
self.spacing_top = value[0]
1124-
self.spacing_right = value[1]
1125-
self.spacing_bottom = value[2]
1126-
self.spacing_left = value[3]
1146+
if value[0] == 'null':
1147+
self.spacing_top = None
1148+
else:
1149+
self.spacing_top = value[0]
1150+
if value[1] == 'null':
1151+
self.spacing_right = None
1152+
else:
1153+
self.spacing_right = value[1]
1154+
if value[2] == 'null':
1155+
self.spacing_bottom = None
1156+
else:
1157+
self.spacing_bottom = value[2]
1158+
if value[3] == 'null':
1159+
self.spacing_left = None
1160+
else:
1161+
self.spacing_left = value[3]
11271162
else:
1128-
value = validators.numeric(value, allow_empty = False)
11291163
self.spacing_top = value
11301164
self.spacing_right = value
11311165
self.spacing_bottom = value

tests/options/chart/test_chart.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,166 @@ def test_to_dict(kwargs, error):
177177
])
178178
def test_from_js_literal(input_files, filename, as_file, error):
179179
Class_from_js_literal(cls, input_files, filename, as_file, error)
180+
181+
182+
@pytest.mark.parametrize('as_dict, as_js_literal, error', [
183+
({
184+
'marginRight': 124
185+
}, False, None),
186+
({
187+
'type': 'bar',
188+
'marginRight': 124,
189+
'marginTop': 421,
190+
'marginBottom': 321,
191+
'marginLeft': 789,
192+
'scrollablePlotArea': {
193+
'minHeight': 1000,
194+
'opacity': 1
195+
}
196+
}, False, None),
197+
198+
({
199+
'marginRight': 124
200+
}, True, None),
201+
({
202+
'type': 'bar',
203+
'marginRight': 124,
204+
'marginTop': 421,
205+
'marginBottom': 321,
206+
'marginLeft': 789,
207+
'scrollablePlotArea': {
208+
'minHeight': 1000,
209+
'opacity': 1
210+
}
211+
}, True, None),
212+
])
213+
def test_bug124_margin_right(as_dict, as_js_literal, error):
214+
if not error:
215+
if not as_js_literal:
216+
result = cls.from_dict(as_dict)
217+
else:
218+
as_str = str(as_dict)
219+
result = cls.from_js_literal(as_str)
220+
assert isinstance(result, cls) is True
221+
if 'marginRight' in as_dict or 'margin_right' in as_dict:
222+
assert result.margin_right == as_dict.get('marginRight', None)
223+
if 'marginTop' in as_dict or 'margin_top' in as_dict:
224+
assert result.margin_top == as_dict.get('marginTop', None)
225+
if 'marginBottom' in as_dict or 'margin_bottom' in as_dict:
226+
assert result.margin_bottom == as_dict.get('marginBottom', None)
227+
if 'marginLeft' in as_dict or 'margin_left' in as_dict:
228+
assert result.margin_left == as_dict.get('marginLeft', None)
229+
else:
230+
with pytest.raises(error):
231+
if not as_js_literal:
232+
result = cls.from_dict(as_dict)
233+
else:
234+
as_str = str(as_dict)
235+
result = cls.from_js_literal(as_str)
236+
237+
238+
@pytest.mark.parametrize('as_str, error', [
239+
("""{
240+
marginRight: 124
241+
}""", None),
242+
("""{type: 'bar',
243+
marginRight: 124,
244+
marginTop: 421,
245+
marginBottom: 321,
246+
marginLeft: 789,
247+
scrollablePlotArea: {
248+
minHeight: 1000,
249+
opacity: 1
250+
}
251+
}""", None),
252+
253+
("""{
254+
marginRight: null
255+
}""", None),
256+
])
257+
def test_bug124_margin_right_from_js_literal(as_str, error):
258+
if not error:
259+
result = cls.from_js_literal(as_str)
260+
assert isinstance(result, cls) is True
261+
if 'marginRight' in as_str or 'margin_right' in as_str:
262+
if 'marginRight: null' not in as_str:
263+
assert result.margin_right is not None
264+
else:
265+
assert result.margin_right is None
266+
if 'marginTop' in as_str or 'margin_top' in as_str:
267+
assert result.margin_top is not None
268+
if 'marginBottom' in as_str or 'margin_bottom' in as_str:
269+
assert result.margin_bottom is not None
270+
if 'marginLeft' in as_str or 'margin_left' in as_str:
271+
assert result.margin_left is not None
272+
else:
273+
with pytest.raises(error):
274+
result = cls.from_js_literal(as_str)
275+
276+
277+
@pytest.mark.parametrize('as_dict, error', [
278+
({
279+
'marginRight': 124
280+
}, None),
281+
({
282+
'type': 'bar',
283+
'marginRight': 124,
284+
'marginTop': 421,
285+
'marginBottom': 321,
286+
'marginLeft': 789,
287+
'scrollablePlotArea': {
288+
'minHeight': 1000,
289+
'opacity': 1
290+
}
291+
}, None),
292+
293+
])
294+
def test_bug124_margin_right_to_dict_from_dict(as_dict, error):
295+
if not error:
296+
initial_result = cls.from_dict(as_dict)
297+
as_new_dict = initial_result.to_dict()
298+
result = cls.from_dict(as_new_dict)
299+
assert isinstance(result, cls) is True
300+
assert result.margin_right == initial_result.margin_right
301+
assert result.margin_top == initial_result.margin_top
302+
assert result.margin_bottom == initial_result.margin_bottom
303+
assert result.margin_left == initial_result.margin_left
304+
else:
305+
with pytest.raises(error):
306+
initial_result = cls.from_dict(as_dict)
307+
as_new_dict = initial_result.to_dict()
308+
result = cls.from_dict(as_new_dict)
309+
310+
311+
@pytest.mark.parametrize('as_dict, error', [
312+
({
313+
'spacingRight': 124
314+
}, None),
315+
({
316+
'type': 'bar',
317+
'spacingRight': 124,
318+
'spacingTop': 421,
319+
'spacingBottom': 321,
320+
'spacingLeft': 789,
321+
'scrollablePlotArea': {
322+
'minHeight': 1000,
323+
'opacity': 1
324+
}
325+
}, None),
326+
327+
])
328+
def test_bug124_spacing_right_to_dict_from_dict(as_dict, error):
329+
if not error:
330+
initial_result = cls.from_dict(as_dict)
331+
as_new_dict = initial_result.to_dict()
332+
result = cls.from_dict(as_new_dict)
333+
assert isinstance(result, cls) is True
334+
assert result.spacing_right == initial_result.spacing_right
335+
assert result.spacing_top == initial_result.spacing_top
336+
assert result.spacing_bottom == initial_result.spacing_bottom
337+
assert result.spacing_left == initial_result.spacing_left
338+
else:
339+
with pytest.raises(error):
340+
initial_result = cls.from_dict(as_dict)
341+
as_new_dict = initial_result.to_dict()
342+
result = cls.from_dict(as_new_dict)

0 commit comments

Comments
 (0)