diff --git a/.circleci/config.yml b/.circleci/config.yml index 15dda14a80b..101357a36da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -310,7 +310,7 @@ jobs: - checkout - run: name: Install tox - command: 'sudo pip install tox requests black pytz decorator retrying inflect' + command: 'sudo pip install retrying tox black inflect' - run: name: Update jupyterlab-plotly version command: 'cd packages/python/plotly; python setup.py updateplotlywidgetversion' diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index 8caae9bae72..3cbbc63966a 100755 --- a/.circleci/create_conda_optional_env.sh +++ b/.circleci/create_conda_optional_env.sh @@ -16,7 +16,7 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then # Create environment # PYTHON_VERSION=3.6 $HOME/miniconda/bin/conda create -n circle_optional --yes python=$PYTHON_VERSION \ -requests six pytz retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython +requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython # Install orca into environment $HOME/miniconda/bin/conda install --yes -n circle_optional -c plotly plotly-orca diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 07e6447cdde..26face3c25f 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -1,9 +1,7 @@ -import datetime import decimal import json as _json import sys import re -import pytz from _plotly_utils.optional_imports import get_module @@ -102,7 +100,7 @@ def default(self, obj): self.encode_as_sage, self.encode_as_numpy, self.encode_as_pandas, - self.encode_as_datetime_v4, + self.encode_as_datetime, self.encode_as_date, self.encode_as_list, # because some values have `tolist` do last. self.encode_as_decimal, @@ -169,42 +167,13 @@ def encode_as_numpy(obj): raise NotEncodable @staticmethod - def encode_as_datetime_v4(obj): + def encode_as_datetime(obj): """Convert datetime objects to iso-format strings""" try: return obj.isoformat() except AttributeError: raise NotEncodable - @staticmethod - def encode_as_datetime(obj): - """Attempt to convert to utc-iso time string using datetime methods.""" - # Since PY36, isoformat() converts UTC - # datetime.datetime objs to UTC T04:00:00 - if not ( - PY36_OR_LATER - and (isinstance(obj, datetime.datetime) and obj.tzinfo is None) - ): - try: - obj = obj.astimezone(pytz.utc) - except ValueError: - # we'll get a value error if trying to convert with naive datetime - pass - except TypeError: - # pandas throws a typeerror here instead of a value error, it's OK - pass - except AttributeError: - # we'll get an attribute error if astimezone DNE - raise NotEncodable - - # now we need to get a nicely formatted time string - try: - time_string = obj.isoformat() - except AttributeError: - raise NotEncodable - else: - return iso_to_plotly_time_string(time_string) - @staticmethod def encode_as_date(obj): """Attempt to convert to utc-iso time string using date methods.""" diff --git a/packages/python/plotly/optional-requirements.txt b/packages/python/plotly/optional-requirements.txt index d173261994d..d0bb959e0d9 100644 --- a/packages/python/plotly/optional-requirements.txt +++ b/packages/python/plotly/optional-requirements.txt @@ -18,6 +18,10 @@ nose==1.3.3 pytest==3.5.1 backports.tempfile==1.0 xarray +pytz + +## orca dependencies ## +requests psutil ## code formatting diff --git a/packages/python/plotly/plotly/io/_orca.py b/packages/python/plotly/plotly/io/_orca.py index 5795daf3e80..2f3608bfb3d 100644 --- a/packages/python/plotly/plotly/io/_orca.py +++ b/packages/python/plotly/plotly/io/_orca.py @@ -1325,6 +1325,20 @@ def ensure_server(): """ ) + # Validate requests + if not get_module("requests"): + raise ValueError( + """\ +Image generation requires the requests package. + +Install using pip: + $ pip install requests + +Install using conda: + $ conda install requests +""" + ) + # Validate orca executable if status.state == "unvalidated": validate_executable() diff --git a/packages/python/plotly/plotly/io/_renderers.py b/packages/python/plotly/plotly/io/_renderers.py index c39d0c32e8a..ba11850cb51 100644 --- a/packages/python/plotly/plotly/io/_renderers.py +++ b/packages/python/plotly/plotly/io/_renderers.py @@ -5,6 +5,7 @@ import six import os +from distutils.version import LooseVersion from plotly import optional_imports @@ -27,6 +28,7 @@ ipython = optional_imports.get_module("IPython") ipython_display = optional_imports.get_module("IPython.display") +nbformat = optional_imports.get_module("nbformat") # Renderer configuration class @@ -374,6 +376,11 @@ def show(fig, renderer=None, validate=True, **kwargs): "Mime type rendering requires ipython but it is not installed" ) + if not nbformat or LooseVersion(nbformat.__version__) < LooseVersion("4.2.0"): + raise ValueError( + "Mime type rendering requires nbformat>=4.2.0 but it is not installed" + ) + ipython_display.display(bundle, raw=True) # external renderers diff --git a/packages/python/plotly/plotly/matplotlylib/mpltools.py b/packages/python/plotly/plotly/matplotlylib/mpltools.py index 2a432bd68be..af2e7d38617 100644 --- a/packages/python/plotly/plotly/matplotlylib/mpltools.py +++ b/packages/python/plotly/plotly/matplotlylib/mpltools.py @@ -5,10 +5,10 @@ """ import math -import warnings +import datetime +import warnings import matplotlib.dates -import pytz def check_bar_match(old_bar, new_bar): @@ -542,7 +542,7 @@ def mpl_dates_to_datestrings(dates, mpl_formatter): if mpl_formatter == "TimeSeries_DateFormatter": try: dates = matplotlib.dates.epoch2num([date * 24 * 60 * 60 for date in dates]) - dates = matplotlib.dates.num2date(dates, tz=pytz.utc) + dates = matplotlib.dates.num2date(dates) except: return _dates @@ -551,7 +551,7 @@ def mpl_dates_to_datestrings(dates, mpl_formatter): # according to mpl --> try num2date(1) else: try: - dates = matplotlib.dates.num2date(dates, tz=pytz.utc) + dates = matplotlib.dates.num2date(dates) except: return _dates diff --git a/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py b/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py index 1587e388bdc..7a8b5ca094e 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py +++ b/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py @@ -5,7 +5,7 @@ import json as _json -from plotly.utils import PlotlyJSONEncoder, get_by_path, memoize, node_generator +from plotly.utils import PlotlyJSONEncoder, get_by_path, node_generator class TestJSONEncoder(TestCase): @@ -48,98 +48,3 @@ def test_node_generator(self): ] for i, item in enumerate(node_generator(node0)): self.assertEqual(item, expected_node_path_tuples[i]) - - -class TestMemoizeDecorator(TestCase): - - # In Python 2.x, globals should be module-scoped. By defining and - # instantiating a class, we *access* the global first before attempting - # to update a value. I.e., you *cannot* simply mutate the global value - # on it's own. - class Namespace(object): - pass - - def test_memoize(self): - name_space = self.Namespace() - name_space.call_count = 0 - - @memoize() - def add(a, b): - name_space.call_count += 1 - return a + b - - tests = [[(1, 1), 2], [(2, 3), 5], [(3, -3), 0]] - - self.assertEqual(name_space.call_count, 0) - for i, (inputs, result) in enumerate(tests, 1): - for _ in range(10): - self.assertEqual(add(*inputs), result) - self.assertEqual(name_space.call_count, i) - - def test_memoize_maxsize(self): - name_space = self.Namespace() - name_space.call_count = 0 - - maxsize = 10 - - @memoize(maxsize=maxsize) - def identity(a): - name_space.call_count += 1 - return a - - # Function hasn't been called yet, we should get *up to* maxsize cache. - for i in range(maxsize): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, i + 1) - - # Nothing should have been discarded yet. no additional calls. - for i in range(maxsize): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, maxsize) - - # Make a new call... - self.assertEqual(identity(maxsize), maxsize) - self.assertEqual(name_space.call_count, maxsize + 1) - - # All but the first call should be remembered. - for i in range(1, maxsize + 1): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, maxsize + 1) - - # The *initial* call should now be forgotten for each new call. - for i in range(maxsize): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, maxsize + 1 + i + 1) - - def test_memoize_maxsize_none(self): - name_space = self.Namespace() - name_space.call_count = 0 - - @memoize(maxsize=None) - def identity(a): - name_space.call_count += 1 - return a - - # Function hasn't been called yet, we should get *up to* maxsize cache. - for i in range(400): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, i + 1) - - # Nothing should have been discarded. no additional calls. - for i in range(400): - self.assertEqual(identity(i), i) - self.assertEqual(name_space.call_count, 400) - - def test_memoize_function_info(self): - # We use the decorator module to assure that function info is not - # overwritten by the decorator. - - @memoize() - def foo(a, b, c="see?"): - """Foo is foo.""" - pass - - self.assertEqual(foo.__doc__, "Foo is foo.") - self.assertEqual(foo.__name__, "foo") - self.assertEqual(getargspec(foo).args, ["a", "b", "c"]) - self.assertEqual(getargspec(foo).defaults, ("see?",)) diff --git a/packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py b/packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py index 4027eb0b2b7..fd5f45ce54d 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py @@ -113,33 +113,23 @@ def test_encode_as_numpy(self): res = utils.PlotlyJSONEncoder.encode_as_numpy(np.ma.core.masked) self.assertTrue(math.isnan(res)) - def test_encode_valid_datetime(self): - - # should *fail* without 'utcoffset' and 'isoformat' and '__sub__' attrs - # non_datetimes = [datetime.date(2013, 10, 1), 'noon', 56, '00:00:00'] - non_datetimes = [datetime.date(2013, 10, 1)] - for obj in non_datetimes: - self.assertRaises( - utils.NotEncodable, utils.PlotlyJSONEncoder.encode_as_datetime, obj - ) - def test_encode_as_datetime(self): # should succeed with 'utcoffset', 'isoformat' and '__sub__' attrs res = utils.PlotlyJSONEncoder.encode_as_datetime(datetime.datetime(2013, 10, 1)) - self.assertEqual(res, "2013-10-01") + self.assertEqual(res, "2013-10-01T00:00:00") def test_encode_as_datetime_with_microsecond(self): # should not include extraneous microsecond info if DNE res = utils.PlotlyJSONEncoder.encode_as_datetime( datetime.datetime(2013, 10, 1, microsecond=0) ) - self.assertEqual(res, "2013-10-01") + self.assertEqual(res, "2013-10-01T00:00:00") # should include microsecond info if present res = utils.PlotlyJSONEncoder.encode_as_datetime( datetime.datetime(2013, 10, 1, microsecond=10) ) - self.assertEqual(res, "2013-10-01 00:00:00.000010") + self.assertEqual(res, "2013-10-01T00:00:00.000010") def test_encode_as_datetime_with_localized_tz(self): # should convert tzinfo to utc. Note that in october, we're in EDT! @@ -148,7 +138,7 @@ def test_encode_as_datetime_with_localized_tz(self): aware_datetime = pytz.timezone("US/Eastern").localize(naive_datetime) res = utils.PlotlyJSONEncoder.encode_as_datetime(aware_datetime) - self.assertEqual(res, "2013-10-01 04:00:00") + self.assertEqual(res, "2013-10-01T00:00:00-04:00") def test_encode_as_date(self): diff --git a/packages/python/plotly/plotly/utils.py b/packages/python/plotly/plotly/utils.py index 5ce9c3765fb..bf58ff756a1 100644 --- a/packages/python/plotly/plotly/utils.py +++ b/packages/python/plotly/plotly/utils.py @@ -1,10 +1,8 @@ from __future__ import absolute_import, division import textwrap -from collections import deque from pprint import PrettyPrinter -from decorator import decorator from _plotly_utils.utils import * @@ -209,47 +207,3 @@ def decode_unicode(coll): pass coll[str(key)] = coll.pop(key) return coll - - -def memoize(maxsize=128): - """ - Memoize a function by its arguments. Note, if the wrapped function returns - a mutable result, the caller is responsible for *not* mutating the result - as it will mutate the cache itself. - - :param (int|None) maxsize: Limit the number of cached results. This is a - simple way to prevent memory leaks. Setting this - to `None` will remember *all* calls. The 128 - number is used for parity with the Python 3.2 - `functools.lru_cache` tool. - - """ - keys = deque() - cache = {} - - def _memoize(*all_args, **kwargs): - func = all_args[0] - args = all_args[1:] - key = _default_memoize_key_function(*args, **kwargs) - - if key in keys: - return cache[key] - - if maxsize is not None and len(keys) == maxsize: - cache.pop(keys.pop()) - - result = func(*args, **kwargs) - keys.appendleft(key) - cache[key] = result - return result - - return decorator(_memoize) - - -def _default_memoize_key_function(*args, **kwargs): - """Factored out in case we want to allow callers to specify this func.""" - if kwargs: - # frozenset is used to ensure hashability - return args, frozenset(kwargs.items()) - else: - return args diff --git a/packages/python/plotly/recipe/meta.yaml b/packages/python/plotly/recipe/meta.yaml index 47cee81cd94..4c985667e69 100644 --- a/packages/python/plotly/recipe/meta.yaml +++ b/packages/python/plotly/recipe/meta.yaml @@ -18,13 +18,9 @@ requirements: - python - pip - nodejs + - setuptools run: - python - - setuptools - - decorator >=4.0.6 - - nbformat >=4.2 - - pytz - - requests - retrying >=1.3.3 - six diff --git a/packages/python/plotly/requirements.txt b/packages/python/plotly/requirements.txt index dfb0bbe4291..496204893c7 100644 --- a/packages/python/plotly/requirements.txt +++ b/packages/python/plotly/requirements.txt @@ -5,14 +5,8 @@ ### ### ################################################### -## http request dependencies ## -requests - ## python 2 to 3 compatibility ## six==1.8.0 -## timezone definitions ## -pytz==2014.9 - ## retrying requests ## retrying==1.3.3 diff --git a/packages/python/plotly/setup.py b/packages/python/plotly/setup.py index ff0e9b36a9b..8c628b627a7 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -489,14 +489,7 @@ def run(self): ), ("etc/jupyter/nbconfig/notebook.d", ["plotlywidget.json"]), ], - install_requires=[ - "decorator>=4.0.6", - "nbformat>=4.2", - "pytz", - "requests", - "retrying>=1.3.3", - "six", - ], + install_requires=["retrying>=1.3.3", "six"], zip_safe=False, cmdclass=dict( build_py=js_prerelease(versioneer_cmds["build_py"]),