diff --git a/circle.yml b/circle.yml index 74a2f68584a..f4909428b1d 100644 --- a/circle.yml +++ b/circle.yml @@ -7,14 +7,20 @@ machine: PLOTLY_OPTIONAL_REQUIREMENTS_FILE: ${PLOTLY_PACKAGE_ROOT}/optional-requirements.txt PLOTLY_OPTIONAL_REQUIREMENTS_FILE_2_6: ${PLOTLY_PACKAGE_ROOT}/optional-requirements-2-6.txt dependencies: - pre: + override: + # run all the pre-written installers (this will take a *while*) - bash circle/setup.sh + # install testing tools for circle's version of things - - PYENV_VERSION=2.7 && pip install nose coverage - override: - - PYENV_VERSION=2.7 && pip install -I . - - PYENV_VERSION=2.7 && cd ~ && python -c "import plotly" + - pip install nose coverage + - pip install -I . + + # we need to cd out of the project root to ensure the install worked + - cd ~ && python -c "import plotly" + + cache_directories: + - "~/.pyenv/versions" # attempt to just cache installed pyenv things test: override: @@ -28,6 +34,6 @@ test: # - sudo chmod 600 ${PLOTLY_CONFIG_DIR} && python -c "import plotly" # test core things in the general 2.7 version that circle has - - PYENV_VERSION=2.7 && nosetests -xv plotly/tests --with-coverage --cover-package=plotly + - nosetests -xv plotly/tests --with-coverage --cover-package=plotly - mkdir "${CIRCLE_ARTIFACTS}/2.7" || true - coverage html -d "${CIRCLE_ARTIFACTS}/2.7" --title=2.7 diff --git a/circle/setup.sh b/circle/setup.sh index d5e715dc95b..84281f1490c 100644 --- a/circle/setup.sh +++ b/circle/setup.sh @@ -12,6 +12,10 @@ function error_exit exit 1 } +# PYENV shims need to be infront of the rest of the path to work! +echo "adding pyenv shims to the beginning of the path in this shell" +export PATH="/home/ubuntu/.pyenv/shims:$PATH" + # for each version we want, setup a functional virtual environment for version in ${PLOTLY_PYTHON_VERSIONS[@]}; do echo Setting up Python ${version} @@ -20,6 +24,11 @@ for version in ${PLOTLY_PYTHON_VERSIONS[@]}; do export PYENV_VERSION=${version} echo "Using pyenv version $(pyenv version)" + # this was a major issue previously, sanity check that we're using the + # version we *think* we're using (that pyenv is pointing to) + echo "python -c 'import sys; print(sys.version)'" + python -c 'import sys; print(sys.version)' + # install core requirements all versions need pip install -r ${PLOTLY_CORE_REQUIREMENTS_FILE} || error_exit "${LINENO}: can't install core reqs for Python ${version}" diff --git a/circle/test.sh b/circle/test.sh index 11dd86c60d1..b31775d5d42 100644 --- a/circle/test.sh +++ b/circle/test.sh @@ -12,6 +12,10 @@ function error_exit exit 1 } +# PYENV shims need to be infront of the rest of the path to work! +echo "adding pyenv shims to the beginning of the path in this shell" +export PATH="/home/ubuntu/.pyenv/shims:$PATH" + # for each version we want, setup a functional virtual environment for version in ${PLOTLY_PYTHON_VERSIONS[@]}; do echo Testing Python ${version} @@ -20,6 +24,12 @@ for version in ${PLOTLY_PYTHON_VERSIONS[@]}; do export PYENV_VERSION=${version} echo "Using pyenv version $(pyenv version)" + # this was a major issue previously, sanity check that we're using the + # version we *think* we're using (that pyenv is pointing to) + echo "python -c 'import sys; print(sys.version)'" + python -c 'import sys; print(sys.version)' + + echo "install plotly (ignoring possibly cached versions)" pip install -I ${PLOTLY_PACKAGE_ROOT} || error_exit "${LINENO}: can't install plotly package from project root" diff --git a/plotly/exceptions.py b/plotly/exceptions.py index c98e630fc58..ff25df04427 100644 --- a/plotly/exceptions.py +++ b/plotly/exceptions.py @@ -15,9 +15,14 @@ """ -import json +import sys import six +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + ## Base Plotly Error ## class PlotlyError(Exception): @@ -49,7 +54,10 @@ def __init__(self, requests_exception): elif content_type == 'text/plain': self.message = requests_exception.response.content else: - self.message = requests_exception.message + try: + self.message = requests_exception.message + except AttributeError: + self.message = 'unknown error' def __str__(self): return self.message diff --git a/plotly/grid_objs/grid_objs.py b/plotly/grid_objs/grid_objs.py index fcba45d4df9..8dc8faaab90 100644 --- a/plotly/grid_objs/grid_objs.py +++ b/plotly/grid_objs/grid_objs.py @@ -5,12 +5,16 @@ """ from __future__ import absolute_import - -import json +import sys from collections import MutableSequence from plotly import exceptions from plotly import utils +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + __all__ = None diff --git a/plotly/plotly/plotly.py b/plotly/plotly/plotly.py index b2e44e19ce0..72b5ee42644 100644 --- a/plotly/plotly/plotly.py +++ b/plotly/plotly/plotly.py @@ -17,7 +17,6 @@ from __future__ import absolute_import import sys -import json import warnings import copy import os @@ -30,6 +29,11 @@ else: from urllib.parse import urlparse +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + from plotly.plotly import chunked_requests from plotly import utils from plotly import tools diff --git a/plotly/tests/test_core/test_get_figure/test_get_figure.py b/plotly/tests/test_core/test_get_figure/test_get_figure.py index 0fc86f49241..85a92d264a4 100644 --- a/plotly/tests/test_core/test_get_figure/test_get_figure.py +++ b/plotly/tests/test_core/test_get_figure/test_get_figure.py @@ -10,10 +10,10 @@ from plotly import exceptions from nose.tools import raises import six -import json from unittest import TestCase -from unittest import skipIf + +version = six.sys.version_info[:2] # need this for conditional testing # username for tests: 'plotlyimagetest' @@ -198,12 +198,18 @@ def test_all(): class TestBytesVStrings(TestCase): - @skipIf(not six.PY3, 'Decoding and missing escapes is only seen in PY3') - def test_proper_escaping(self): - un = 'PlotlyImageTest' - ak = '786r5mecv0' - url = "https://plot.ly/~PlotlyImageTest/91/" - py.sign_in(un, ak) - print("getting: https://plot.ly/~PlotlyImageTest/91/") - print("###########################################\n\n") - fig = py.get_figure(url) + # unittest `skipIf` not supported in 2.6 + if version < (2, 7) or (2, 7) < version < (3, 3): + pass + else: + from unittest import skipIf + + @skipIf(not six.PY3, 'Decoding and missing escapes only seen in PY3') + def test_proper_escaping(self): + un = 'PlotlyImageTest' + ak = '786r5mecv0' + url = "https://plot.ly/~PlotlyImageTest/91/" + py.sign_in(un, ak) + print("getting: https://plot.ly/~PlotlyImageTest/91/") + print("###########################################\n\n") + fig = py.get_figure(url) diff --git a/plotly/tests/test_core/test_get_requests/test_get_requests.py b/plotly/tests/test_core/test_get_requests/test_get_requests.py index c40f15a951d..473fcd15ceb 100644 --- a/plotly/tests/test_core/test_get_requests/test_get_requests.py +++ b/plotly/tests/test_core/test_get_requests/test_get_requests.py @@ -8,8 +8,13 @@ import requests import copy -import json import six +import sys + +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json default_headers = {'plotly-username': '', 'plotly-apikey': '', diff --git a/plotly/tests/test_core/test_utils/test_utils.py b/plotly/tests/test_core/test_utils/test_utils.py index 5f0a62d88ed..94be40b3949 100644 --- a/plotly/tests/test_core/test_utils/test_utils.py +++ b/plotly/tests/test_core/test_utils/test_utils.py @@ -1,8 +1,13 @@ -import json +import sys from unittest import TestCase from plotly.utils import PlotlyJSONEncoder +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + class TestJSONEncoder(TestCase): diff --git a/plotly/tests/test_optional/test_ipython/test_embed.py b/plotly/tests/test_optional/test_ipython/test_embed.py index 587b3f20a8e..f9a69527828 100644 --- a/plotly/tests/test_optional/test_ipython/test_embed.py +++ b/plotly/tests/test_optional/test_ipython/test_embed.py @@ -5,13 +5,14 @@ import threading import six import unittest -from unittest import skip version = six.sys.version_info[:2] # need this for conditional testing # unittest `skipIf` not supported in 2.6 and IPython not supported in 2.6/3.2 if version < (2, 7) or (2, 7) < version < (3, 3): pass else: + from unittest import skip + @skip class TestPlotlyDisplay(unittest.TestCase): diff --git a/plotly/tests/test_optional/test_ipython/test_widgets.py b/plotly/tests/test_optional/test_ipython/test_widgets.py index 6d54403065b..e3b45761da0 100644 --- a/plotly/tests/test_optional/test_ipython/test_widgets.py +++ b/plotly/tests/test_optional/test_ipython/test_widgets.py @@ -1,9 +1,16 @@ +import six from unittest import TestCase -from plotly.widgets import GraphWidget +version = six.sys.version_info[:2] # need this for conditional testing +# unittest `skip` not supported in 2.6 and IPython not supported in 2.6/3.2 +if version < (2, 7) or (2, 7) < version < (3, 3): + pass +else: + from plotly.widgets import GraphWidget -class TestWidgets(TestCase): - def test_instantiate_graph_widget(self): - widget = GraphWidget + class TestWidgets(TestCase): + + def test_instantiate_graph_widget(self): + widget = GraphWidget diff --git a/plotly/tests/test_optional/test_matplotlylib/test_date_times.py b/plotly/tests/test_optional/test_matplotlylib/test_date_times.py index 75ff1605d26..5e05f3ba4fc 100644 --- a/plotly/tests/test_optional/test_matplotlylib/test_date_times.py +++ b/plotly/tests/test_optional/test_matplotlylib/test_date_times.py @@ -64,4 +64,4 @@ def test_pandas_time_series_date_formatter(self): x0 = fig.axes[0].lines[0].get_xydata()[0][0] self.assertEqual(x0, expected_x0) - self.assertListEqual(pfig['data'][0]['x'], expected_x) + self.assertEqual(pfig['data'][0]['x'], expected_x) diff --git a/plotly/tests/test_optional/test_utils/test_utils.py b/plotly/tests/test_optional/test_utils/test_utils.py index a1450913860..db3d8dc3f56 100644 --- a/plotly/tests/test_optional/test_utils/test_utils.py +++ b/plotly/tests/test_optional/test_utils/test_utils.py @@ -11,6 +11,7 @@ import numpy as np import json import pandas as pd +import sys from pandas.util.testing import assert_series_equal import matplotlib.pyplot as plt @@ -27,7 +28,7 @@ def test_encode_as_plotly(self): # should *fail* when object doesn't have `to_plotly_json` attribute objs_without_attr = [ - 1, 'one', {'a', 'set'}, {'a': 'dict'}, ['a', 'list'] + 1, 'one', set(['a', 'set']), {'a': 'dict'}, ['a', 'list'] ] for obj in objs_without_attr: self.assertRaises(utils.NotEncodable, @@ -48,7 +49,7 @@ def test_encode_as_list(self): # should *fail* when object doesn't have `tolist` method objs_without_attr = [ - 1, 'one', {'a', 'set'}, {'a': 'dict'}, ['a', 'list'] + 1, 'one', set(['a', 'set']), {'a': 'dict'}, ['a', 'list'] ] for obj in objs_without_attr: self.assertRaises(utils.NotEncodable, @@ -75,7 +76,7 @@ def test_encode_as_pandas(self): # should succeed when we've got specific pandas thingies res = utils.PlotlyJSONEncoder.encode_as_pandas(pd.NaT) - self.assertIs(res, None) + self.assertTrue(res is None) def test_encode_as_numpy(self): @@ -132,7 +133,7 @@ def test_encode_as_date(self): utils.PlotlyJSONEncoder.encode_as_date, obj) # should work with a date - a_date = datetime.date(2013, 10, 01) + a_date = datetime.date(2013, 10, 1) res = utils.PlotlyJSONEncoder.encode_as_date(a_date) self.assertEqual(res, '2013-10-01') @@ -248,9 +249,8 @@ def test_pandas_json_encoding(): def test_numpy_masked_json_encoding(): l = [1, 2, np.ma.core.masked] j1 = json.dumps(l, cls=utils.PlotlyJSONEncoder) - print j1 + print(j1) assert(j1 == '[1, 2, null]') - assert(set(l) == set([1, 2, np.ma.core.masked])) def test_masked_constants_example(): @@ -275,8 +275,9 @@ def test_masked_constants_example(): jy = json.dumps(renderer.plotly_fig['data'][1]['y'], cls=utils.PlotlyJSONEncoder) - assert(jy == '[-398.11793026999999, -398.11792966000002, ' - '-398.11786308000001, null]') + print(jy) + array = json.loads(jy) + assert(array == [-398.11793027, -398.11792966, -398.11786308, None]) def test_numpy_dates(): diff --git a/plotly/utils.py b/plotly/utils.py index e948a07507f..c8a38aceabc 100644 --- a/plotly/utils.py +++ b/plotly/utils.py @@ -6,7 +6,6 @@ """ -import json import os.path import sys import threading @@ -32,6 +31,11 @@ except ImportError: _sage_imported = False +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + ### incase people are using threading, we lock file reads lock = threading.Lock() @@ -124,66 +128,45 @@ class PlotlyJSONEncoder(json.JSONEncoder): version. """ + def coerce_to_strict(self, const): + """ + This is used to ultimately *encode* into strict JSON, see `encode` - # we want stricter JSON, so convert NaN, Inf, -Inf --> 'null' - nan_str = inf_str = neg_inf_str = 'null' - - # uses code from official python json.encoder module. Same licence applies. - def iterencode(self, o, _one_shot=False): """ - Encode the given object and yield each string - representation as available. + # before python 2.7, 'true', 'false', 'null', were include here. + if const in ('Infinity', '-Infinity', 'NaN'): + return None + else: + return const - For example:: + def encode(self, o): + """ + Load and then dump the result using parse_constant kwarg - for chunk in JSONEncoder().iterencode(bigobject): - mysocket.write(chunk) + Note that setting invalid separators will cause a failure at this step. """ - if self.check_circular: - markers = {} - else: - markers = None - if self.ensure_ascii: - _encoder = json.encoder.encode_basestring_ascii - else: - _encoder = json.encoder.encode_basestring - if self.encoding != 'utf-8': - def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): - if isinstance(o, str): - o = o.decode(_encoding) - return _orig_encoder(o) - - def floatstr(o, allow_nan=self.allow_nan, - _repr=json.encoder.FLOAT_REPR, _inf=json.encoder.INFINITY, - _neginf=-json.encoder.INFINITY): - # Check for specials. Note that this type of test is processor - # and/or platform-specific, so do tests which don't depend on the - # internals. - - # *any* two NaNs are not equivalent (even to itself) try: - # float('NaN') == float('NaN') - if o != o: - text = self.nan_str - elif o == _inf: - text = self.inf_str - elif o == _neginf: - text = self.neg_inf_str - else: - return _repr(o) - if not allow_nan: - raise ValueError( - "Out of range float values are not JSON compliant: " + - repr(o)) + # this will raise errors in a normal-expected way + encoded_o = super(PlotlyJSONEncoder, self).encode(o) - return text + # now: + # 1. `loads` to switch Infinity, -Infinity, NaN to None + # 2. `dumps` again so you get 'null' instead of extended JSON + try: + new_o = json.loads(encoded_o, parse_constant=self.coerce_to_strict) + except ValueError: - _iterencode = json.encoder._make_iterencode( - markers, self.default, _encoder, self.indent, floatstr, - self.key_separator, self.item_separator, self.sort_keys, - self.skipkeys, _one_shot) - return _iterencode(o, 0) + # invalid separators will fail here. raise a helpful exception + raise ValueError( + "Encoding into strict JSON failed. Did you set the separators " + "valid JSON separators?" + ) + else: + return json.dumps(new_o, sort_keys=self.sort_keys, + indent=self.indent, + separators=(self.item_separator, + self.key_separator)) def default(self, obj): """ diff --git a/plotly/version.py b/plotly/version.py index 9a5e84e0a7a..e226d1e9f91 100644 --- a/plotly/version.py +++ b/plotly/version.py @@ -1 +1 @@ -__version__ = '1.6.11' +__version__ = '1.6.12' diff --git a/plotly/widgets/graph_widget.py b/plotly/widgets/graph_widget.py index 22c5c28434f..89e22769170 100644 --- a/plotly/widgets/graph_widget.py +++ b/plotly/widgets/graph_widget.py @@ -1,6 +1,6 @@ from collections import deque -import json import uuid +import sys # TODO: protected imports? from IPython.html import widgets @@ -11,6 +11,12 @@ from plotly.graph_objs import Figure from pkg_resources import resource_string +# even though python 2.6 wouldn't be able to run *any* of this... +if sys.version[:3] == '2.6': + import simplejson as json +else: + import json + # Load JS widget code # No officially recommended way to do this in any other way # http://mail.scipy.org/pipermail/ipython-dev/2014-April/013835.html