From b4a93d3210ea719428d42923e0ff61f7fe4a418b Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 12:39:30 -0400 Subject: [PATCH 1/8] Remove nbformat as a hard dependency and add runtime check --- packages/python/plotly/plotly/io/_renderers.py | 7 +++++++ packages/python/plotly/setup.py | 9 +-------- 2 files changed, 8 insertions(+), 8 deletions(-) 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/setup.py b/packages/python/plotly/setup.py index ff0e9b36a9b..ceaecf33caa 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=["decorator>=4.0.6", "pytz", "requests", "retrying>=1.3.3", "six"], zip_safe=False, cmdclass=dict( build_py=js_prerelease(versioneer_cmds["build_py"]), From 1958f9a314614739c1fc994c48ee4307c83a282c Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 12:51:08 -0400 Subject: [PATCH 2/8] Remove v3 datetime serialization and remove pytz runtime dependency --- packages/python/plotly/_plotly_utils/utils.py | 35 ++----------------- .../python/plotly/optional-requirements.txt | 1 + .../plotly/plotly/matplotlylib/mpltools.py | 8 ++--- .../test_optional/test_utils/test_utils.py | 18 +++------- packages/python/plotly/requirements.txt | 3 -- packages/python/plotly/setup.py | 2 +- 6 files changed, 12 insertions(+), 55 deletions(-) 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..cf504b10ac9 100644 --- a/packages/python/plotly/optional-requirements.txt +++ b/packages/python/plotly/optional-requirements.txt @@ -19,6 +19,7 @@ pytest==3.5.1 backports.tempfile==1.0 xarray psutil +pytz ## code formatting pre-commit diff --git a/packages/python/plotly/plotly/matplotlylib/mpltools.py b/packages/python/plotly/plotly/matplotlylib/mpltools.py index 2a432bd68be..b779aebe0a0 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, tz=datetime.timezone.utc) 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, tz=datetime.timezone.utc) except: return _dates 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/requirements.txt b/packages/python/plotly/requirements.txt index dfb0bbe4291..36b2740806b 100644 --- a/packages/python/plotly/requirements.txt +++ b/packages/python/plotly/requirements.txt @@ -11,8 +11,5 @@ 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 ceaecf33caa..7ba82943c70 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -489,7 +489,7 @@ def run(self): ), ("etc/jupyter/nbconfig/notebook.d", ["plotlywidget.json"]), ], - install_requires=["decorator>=4.0.6", "pytz", "requests", "retrying>=1.3.3", "six"], + install_requires=["decorator>=4.0.6", "requests", "retrying>=1.3.3", "six"], zip_safe=False, cmdclass=dict( build_py=js_prerelease(versioneer_cmds["build_py"]), From c3c22cfbb52375265410929d564cbff33c595579 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 12:56:09 -0400 Subject: [PATCH 3/8] Remove unused memoize function and the decorator dependency --- .circleci/config.yml | 2 +- .../tests/test_core/test_utils/test_utils.py | 97 +------------------ packages/python/plotly/plotly/utils.py | 46 --------- packages/python/plotly/setup.py | 2 +- 4 files changed, 3 insertions(+), 144 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 15dda14a80b..9b116ea4200 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 tox requests black retrying inflect' - run: name: Update jupyterlab-plotly version command: 'cd packages/python/plotly; python setup.py updateplotlywidgetversion' 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/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/setup.py b/packages/python/plotly/setup.py index 7ba82943c70..f7db472189c 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -489,7 +489,7 @@ def run(self): ), ("etc/jupyter/nbconfig/notebook.d", ["plotlywidget.json"]), ], - install_requires=["decorator>=4.0.6", "requests", "retrying>=1.3.3", "six"], + install_requires=["requests", "retrying>=1.3.3", "six"], zip_safe=False, cmdclass=dict( build_py=js_prerelease(versioneer_cmds["build_py"]), From e7b8ad212b6f4e203957386bec763d5510c41bf8 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 13:03:04 -0400 Subject: [PATCH 4/8] Make requests an optional dependency (only used for orca image export) --- packages/python/plotly/optional-requirements.txt | 5 ++++- packages/python/plotly/plotly/io/_orca.py | 14 ++++++++++++++ packages/python/plotly/requirements.txt | 3 --- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/optional-requirements.txt b/packages/python/plotly/optional-requirements.txt index cf504b10ac9..d0bb959e0d9 100644 --- a/packages/python/plotly/optional-requirements.txt +++ b/packages/python/plotly/optional-requirements.txt @@ -18,9 +18,12 @@ nose==1.3.3 pytest==3.5.1 backports.tempfile==1.0 xarray -psutil pytz +## orca dependencies ## +requests +psutil + ## code formatting pre-commit 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/requirements.txt b/packages/python/plotly/requirements.txt index 36b2740806b..496204893c7 100644 --- a/packages/python/plotly/requirements.txt +++ b/packages/python/plotly/requirements.txt @@ -5,9 +5,6 @@ ### ### ################################################### -## http request dependencies ## -requests - ## python 2 to 3 compatibility ## six==1.8.0 From b26d2632e71a743e6ce81768b99bdf64d46a6de5 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 13:05:18 -0400 Subject: [PATCH 5/8] update circeci config to remove unneeded dependencies --- .circleci/config.yml | 2 +- .circleci/create_conda_optional_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9b116ea4200..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 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..74c8e49ec33 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 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 From fa9f64ba217dee0d37990f1aaa31bd48c498163b Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 13:20:22 -0400 Subject: [PATCH 6/8] Update dependencies in setup.py and conda recipe --- packages/python/plotly/recipe/meta.yaml | 6 +----- packages/python/plotly/setup.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) 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/setup.py b/packages/python/plotly/setup.py index f7db472189c..8c628b627a7 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -489,7 +489,7 @@ def run(self): ), ("etc/jupyter/nbconfig/notebook.d", ["plotlywidget.json"]), ], - install_requires=["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"]), From c89b664dee74af3ab1c4aebf602edb0a822e1d70 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 13:36:54 -0400 Subject: [PATCH 7/8] Add nbformat back in orca test suite --- .circleci/create_conda_optional_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index 74c8e49ec33..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 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 From 5a90fe1f42020d21259755da0e0ec9d5228fba4c Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 19 Jun 2019 13:45:28 -0400 Subject: [PATCH 8/8] Fix matplotlib datetime tests --- packages/python/plotly/plotly/matplotlylib/mpltools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/matplotlylib/mpltools.py b/packages/python/plotly/plotly/matplotlylib/mpltools.py index b779aebe0a0..af2e7d38617 100644 --- a/packages/python/plotly/plotly/matplotlylib/mpltools.py +++ b/packages/python/plotly/plotly/matplotlylib/mpltools.py @@ -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=datetime.timezone.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=datetime.timezone.utc) + dates = matplotlib.dates.num2date(dates) except: return _dates