Skip to content

Commit f6bb1e2

Browse files
authored
Merge branch 'develop' into enhancement/17-add-support-for-highcharts-core-js-v11
2 parents 69dce9a + de47c2b commit f6bb1e2

File tree

9 files changed

+178
-30
lines changed

9 files changed

+178
-30
lines changed

CHANGES.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ Release 1.1.0
3434

3535
-------------------------------
3636

37+
Release 1.0.2
38+
=========================================
39+
40+
* **DOCUMENTATION:** Added documentation of hard dependencies to the README (issue #37).
41+
42+
-----------------------
43+
44+
Release 1.0.1
45+
=========================================
46+
47+
* **BUGFIX**: Fixed a bug encountered when parsing CSV data (issue #32).
48+
* **ENHANCEMENT**: Added a catch for when trying to set ``Chart.options`` to a ``SharedOptions`` instance (issue #34).
49+
* Fixed a broken link in the documentation.
50+
51+
---------------
52+
3753
Release 1.0.0
3854
=========================================
3955

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ To install **Highcharts Core for Python**, just execute:
6363
6464
$ pip install highcharts-core
6565
66+
Before you install, please be aware of the following "hard" dependencies:
67+
68+
* Python 3.10 or higher
69+
* Highcharts Core (JS) v.10.2 or higher (not technically a Python dependency, but
70+
it won't work with earlier versions of Highcharts)
71+
* `esprima-python <https://github.com/Kronuz/esprima-python>`__ v.4.0 or higher
72+
* `requests <https://requests.readthedocs.io/en/latest/>`__ v.2.28 or higher
73+
* `validator-collection <https://validator-collection.readthedocs.io/en/latest/>`__
74+
v.1.5 or higher
75+
76+
You can find more information about soft and development dependencies in the
77+
`complete documentation <https://core-docs.highchartspython.com/en/latest/#dependencies>`__.
6678

6779
-------------
6880

highcharts_core/__version__.py

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

highcharts_core/chart.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,18 @@ def options(self) -> Optional[HighchartsOptions]:
185185
return self._options
186186

187187
@options.setter
188-
@class_sensitive(HighchartsOptions)
189188
def options(self, value):
189+
if not value:
190+
self._options = None
191+
elif isinstance(value, SharedOptions):
192+
raise errors.HighchartsValueError('Chart.options expects a HighchartsOptions instance '
193+
'or a valid descendent. However, the value you supplied '
194+
'is a SharedOptions instance, which wil a descendent is not '
195+
'valid for this parameter.')
196+
else:
197+
value = validate_types(value,
198+
types = HighchartsOptions)
199+
190200
self._options = value
191201

192202
@property

highcharts_core/options/series/base.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,6 @@ def load_from_csv(self,
441441
except AttributeError:
442442
pass
443443

444-
if checkers.is_file_on_filesystem(as_string_or_file):
445-
with open(as_string_or_file, 'r') as file_:
446-
as_str = file_.read()
447-
else:
448-
as_str = as_string_or_file
449-
450444
property_column_map = validators.dict(property_column_map, allow_empty = False)
451445
cleaned_column_map = {}
452446
for key in property_column_map:
@@ -465,17 +459,32 @@ def load_from_csv(self,
465459
f'instead.')
466460
cleaned_column_map[key] = map_value
467461

468-
columns, csv_records = utility_functions.parse_csv(
469-
as_str,
470-
has_header_row = has_header_row,
471-
delimiter = delimiter,
472-
null_text = null_text,
473-
wrapper_character = wrapper_character,
474-
line_terminator = line_terminator,
475-
wrap_all_strings = False,
476-
double_wrapper_character_when_nested = False,
477-
escape_character = "\\"
478-
)
462+
if not checkers.is_on_filesystem(as_string_or_file):
463+
as_str = as_string_or_file
464+
columns, csv_records = utility_functions.parse_csv(
465+
as_str,
466+
has_header_row = has_header_row,
467+
delimiter = delimiter,
468+
null_text = null_text,
469+
wrapper_character = wrapper_character,
470+
line_terminator = line_terminator,
471+
wrap_all_strings = False,
472+
double_wrapper_character_when_nested = False,
473+
escape_character = "\\"
474+
)
475+
else:
476+
with open(as_string_or_file, 'r', newline = '') as file_:
477+
columns, csv_records = utility_functions.parse_csv(
478+
file_,
479+
has_header_row = has_header_row,
480+
delimiter = delimiter,
481+
null_text = null_text,
482+
wrapper_character = wrapper_character,
483+
line_terminator = line_terminator,
484+
wrap_all_strings = False,
485+
double_wrapper_character_when_nested = False,
486+
escape_character = "\\"
487+
)
479488

480489
for key in cleaned_column_map:
481490
map_value = cleaned_column_map[key]
@@ -496,7 +505,14 @@ def load_from_csv(self,
496505
data_point_dict = {}
497506
for key in cleaned_column_map:
498507
map_value = cleaned_column_map[key]
499-
data_point_dict[key] = record.get(map_value, None)
508+
value = record.get(map_value, None)
509+
if value and isinstance(value, str) and ',' in value:
510+
test_value = value.replace(delimiter, '')
511+
if checkers.is_numeric(test_value):
512+
value = test_value
513+
514+
data_point_dict[key] = value
515+
500516
data_point_dicts.append(data_point_dict)
501517

502518
self.data = data_point_dicts
@@ -651,14 +667,14 @@ def from_csv(cls,
651667
instance = cls(**series_kwargs)
652668
instance.load_from_csv(as_string_or_file,
653669
property_column_map,
654-
has_header_row = True,
655-
delimiter = ',',
656-
null_text = 'None',
657-
wrapper_character = "'",
658-
line_terminator = '\r\n',
659-
wrap_all_strings = False,
660-
double_wrapper_character_when_nested = False,
661-
escape_character = "\\")
670+
has_header_row = has_header_row,
671+
delimiter = delimiter,
672+
null_text = null_text,
673+
wrapper_character = wrapper_character,
674+
line_terminator = line_terminator,
675+
wrap_all_strings = wrap_all_strings,
676+
double_wrapper_character_when_nested = double_wrapper_character_when_nested,
677+
escape_character = escape_character)
662678

663679
return instance
664680

highcharts_core/utility_functions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,14 @@ def parse_csv(csv_data,
279279
:rtype: :class:`tuple <python:tuple>` of a :class:`list <python:list>` of column names
280280
and :class:`list <python:list>` of :class:`dict <python:dict>`
281281
"""
282-
csv_data = validators.string(csv_data, allow_empty = True)
283282
if not csv_data:
284283
return [], []
285284

285+
if isinstance(csv_data, str):
286+
csv_data = csv_data.split(line_terminator)
287+
286288
if not wrapper_character:
287-
wrapper_character = '\''
289+
wrapper_character = "'"
288290

289291
if wrap_all_strings:
290292
quoting = csv.QUOTE_NONNUMERIC

tests/options/series/test_area.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import datetime
66
from json.decoder import JSONDecodeError
77

8+
from validator_collection import checkers
9+
810
from highcharts_core.options.series.area import AreaSeries as cls
911
from highcharts_core.options.series.area import AreaRangeSeries as cls2
1012
from highcharts_core.options.series.area import AreaSplineSeries as cls3
@@ -2764,6 +2766,64 @@ def test_LineSeries_from_pandas(input_files, filename, property_map, error):
27642766
with pytest.raises(error):
27652767
result = cls5.from_pandas(df, property_map = property_map)
27662768

2769+
2770+
@pytest.mark.parametrize('kwargs, error', [
2771+
({
2772+
'as_string_or_file': "'Date','HeadCount'\r\n'01/01/2023','2'\r\n'01/02/2023','4'\r\n'01/03/2023','8'",
2773+
'property_column_map': {'x': 'Date', 'y': 'HeadCount', 'id': 'Date'}
2774+
}, None),
2775+
({
2776+
'as_string_or_file': "Date,HeadCount\r\n01/01/2023,2\r\n01/02/2023,4\r\n01/03/2023,8",
2777+
'property_column_map': {'x': 'Date', 'y': 'HeadCount', 'id': 'Date'}
2778+
}, None),
2779+
2780+
])
2781+
def test_bugfix32_LineSeries_from_csv(kwargs, error):
2782+
if not error:
2783+
result = cls5.from_csv(**kwargs)
2784+
assert result is not None
2785+
assert isinstance(result, cls5) is True
2786+
assert result.data is not None
2787+
assert isinstance(result.data, list) is True
2788+
for item in result.data:
2789+
assert item.x is not None
2790+
assert item.y is not None
2791+
2792+
2793+
@pytest.mark.parametrize('filename, property_map, kwargs, error', [
2794+
('test-data-files/nst-est2019-01.csv', {}, {}, ValueError),
2795+
('test-data-files/nst-est2019-01.csv',
2796+
{
2797+
'name': 'Geographic Area',
2798+
'x': 'Geographic Area',
2799+
'y': '2010'
2800+
},
2801+
{
2802+
'wrapper_character': '"'
2803+
},
2804+
None),
2805+
2806+
])
2807+
def test_LineSeries_from_csv(input_files, filename, property_map, kwargs, error):
2808+
input_file = check_input_file(input_files, filename)
2809+
2810+
if not error:
2811+
result = cls5.from_csv(input_file,
2812+
property_column_map = property_map,
2813+
**kwargs)
2814+
assert result is not None
2815+
assert isinstance(result, cls5)
2816+
assert result.data is not None
2817+
for item in result.data:
2818+
for key in property_map:
2819+
assert getattr(item, key, None) is not None
2820+
else:
2821+
with pytest.raises(error):
2822+
result = cls5.from_csv(input_file,
2823+
property_column_map = property_map,
2824+
**kwargs)
2825+
2826+
27672827
#### NEXT CLASS
27682828

27692829
@pytest.mark.parametrize('kwargs, error', STANDARD_PARAMS)

tests/test_chart.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from json.decoder import JSONDecodeError
66

77
from highcharts_core.chart import Chart as cls
8+
from highcharts_core.global_options.shared_options import SharedOptions
89
from highcharts_core import errors
910
from tests.fixtures import input_files, check_input_file, to_camelCase, to_js_dict, \
1011
Class__init__, Class__to_untrimmed_dict, Class_from_dict, Class_to_dict, \
@@ -20,6 +21,19 @@ def test__init__(kwargs, error):
2021
Class__init__(cls, kwargs, error)
2122

2223

24+
@pytest.mark.parametrize('kwargs, error', [
25+
({}, errors.HighchartsValueError),
26+
])
27+
def test_bugfix34_SharedOptions_error(kwargs, error):
28+
shared_options = SharedOptions()
29+
instance = cls(**kwargs)
30+
if not error:
31+
instance.options = shared_options
32+
else:
33+
with pytest.raises(error):
34+
instance.options = shared_options
35+
36+
2337
@pytest.mark.parametrize('kwargs, error', STANDARD_PARAMS)
2438
def test__to_untrimmed_dict(kwargs, error):
2539
Class__to_untrimmed_dict(cls, kwargs, error)

tests/test_utility_functions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,21 @@
55
from abc import ABC, abstractmethod
66

77
from highcharts_core import utility_functions
8+
9+
10+
@pytest.mark.parametrize('kwargs, expected_column_names, expected_records, error', [
11+
({
12+
'csv_data': "Date,Header\r\n01/01/2023,2\r\n01/02/2023,4\r\n01/03/2023,8"
13+
}, ['Date', 'HeadCount'], 3, None),
14+
])
15+
def test_parse_csv(kwargs, expected_column_names, expected_records, error):
16+
if not error:
17+
columns, records_as_dicts = utility_functions.parse_csv(**kwargs)
18+
assert columns is not None
19+
assert len(columns) == len(expected_column_names)
20+
21+
assert records_as_dicts is not None
22+
assert len(records_as_dicts) == expected_records
23+
else:
24+
with pytest.raises(error):
25+
result = utility_functions.parse_csv(**kwargs)

0 commit comments

Comments
 (0)