Skip to content

Commit b90126e

Browse files
authored
V4 refine dependencies (#1631)
* Remove nbformat as a hard dependency and add runtime check * Remove v3 datetime serialization and remove pytz runtime dependency * Remove unused memoize function and the decorator dependency * Make requests an optional dependency (only used for orca image export) * update circeci config to remove unneeded dependencies * Update dependencies in setup.py and conda recipe * Fix matplotlib datetime tests
1 parent a6005e0 commit b90126e

File tree

13 files changed

+40
-214
lines changed

13 files changed

+40
-214
lines changed

Diff for: .circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ jobs:
310310
- checkout
311311
- run:
312312
name: Install tox
313-
command: 'sudo pip install tox requests black pytz decorator retrying inflect'
313+
command: 'sudo pip install retrying tox black inflect'
314314
- run:
315315
name: Update jupyterlab-plotly version
316316
command: 'cd packages/python/plotly; python setup.py updateplotlywidgetversion'

Diff for: .circleci/create_conda_optional_env.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then
1616
# Create environment
1717
# PYTHON_VERSION=3.6
1818
$HOME/miniconda/bin/conda create -n circle_optional --yes python=$PYTHON_VERSION \
19-
requests six pytz retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython
19+
requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython
2020

2121
# Install orca into environment
2222
$HOME/miniconda/bin/conda install --yes -n circle_optional -c plotly plotly-orca

Diff for: packages/python/plotly/_plotly_utils/utils.py

+2-33
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import datetime
21
import decimal
32
import json as _json
43
import sys
54
import re
6-
import pytz
75

86
from _plotly_utils.optional_imports import get_module
97

@@ -102,7 +100,7 @@ def default(self, obj):
102100
self.encode_as_sage,
103101
self.encode_as_numpy,
104102
self.encode_as_pandas,
105-
self.encode_as_datetime_v4,
103+
self.encode_as_datetime,
106104
self.encode_as_date,
107105
self.encode_as_list, # because some values have `tolist` do last.
108106
self.encode_as_decimal,
@@ -169,42 +167,13 @@ def encode_as_numpy(obj):
169167
raise NotEncodable
170168

171169
@staticmethod
172-
def encode_as_datetime_v4(obj):
170+
def encode_as_datetime(obj):
173171
"""Convert datetime objects to iso-format strings"""
174172
try:
175173
return obj.isoformat()
176174
except AttributeError:
177175
raise NotEncodable
178176

179-
@staticmethod
180-
def encode_as_datetime(obj):
181-
"""Attempt to convert to utc-iso time string using datetime methods."""
182-
# Since PY36, isoformat() converts UTC
183-
# datetime.datetime objs to UTC T04:00:00
184-
if not (
185-
PY36_OR_LATER
186-
and (isinstance(obj, datetime.datetime) and obj.tzinfo is None)
187-
):
188-
try:
189-
obj = obj.astimezone(pytz.utc)
190-
except ValueError:
191-
# we'll get a value error if trying to convert with naive datetime
192-
pass
193-
except TypeError:
194-
# pandas throws a typeerror here instead of a value error, it's OK
195-
pass
196-
except AttributeError:
197-
# we'll get an attribute error if astimezone DNE
198-
raise NotEncodable
199-
200-
# now we need to get a nicely formatted time string
201-
try:
202-
time_string = obj.isoformat()
203-
except AttributeError:
204-
raise NotEncodable
205-
else:
206-
return iso_to_plotly_time_string(time_string)
207-
208177
@staticmethod
209178
def encode_as_date(obj):
210179
"""Attempt to convert to utc-iso time string using date methods."""

Diff for: packages/python/plotly/optional-requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ nose==1.3.3
1818
pytest==3.5.1
1919
backports.tempfile==1.0
2020
xarray
21+
pytz
22+
23+
## orca dependencies ##
24+
requests
2125
psutil
2226

2327
## code formatting

Diff for: packages/python/plotly/plotly/io/_orca.py

+14
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,20 @@ def ensure_server():
13251325
"""
13261326
)
13271327

1328+
# Validate requests
1329+
if not get_module("requests"):
1330+
raise ValueError(
1331+
"""\
1332+
Image generation requires the requests package.
1333+
1334+
Install using pip:
1335+
$ pip install requests
1336+
1337+
Install using conda:
1338+
$ conda install requests
1339+
"""
1340+
)
1341+
13281342
# Validate orca executable
13291343
if status.state == "unvalidated":
13301344
validate_executable()

Diff for: packages/python/plotly/plotly/io/_renderers.py

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import six
77
import os
8+
from distutils.version import LooseVersion
89

910
from plotly import optional_imports
1011

@@ -27,6 +28,7 @@
2728

2829
ipython = optional_imports.get_module("IPython")
2930
ipython_display = optional_imports.get_module("IPython.display")
31+
nbformat = optional_imports.get_module("nbformat")
3032

3133

3234
# Renderer configuration class
@@ -374,6 +376,11 @@ def show(fig, renderer=None, validate=True, **kwargs):
374376
"Mime type rendering requires ipython but it is not installed"
375377
)
376378

379+
if not nbformat or LooseVersion(nbformat.__version__) < LooseVersion("4.2.0"):
380+
raise ValueError(
381+
"Mime type rendering requires nbformat>=4.2.0 but it is not installed"
382+
)
383+
377384
ipython_display.display(bundle, raw=True)
378385

379386
# external renderers

Diff for: packages/python/plotly/plotly/matplotlylib/mpltools.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
66
"""
77
import math
8-
import warnings
8+
import datetime
99

10+
import warnings
1011
import matplotlib.dates
11-
import pytz
1212

1313

1414
def check_bar_match(old_bar, new_bar):
@@ -542,7 +542,7 @@ def mpl_dates_to_datestrings(dates, mpl_formatter):
542542
if mpl_formatter == "TimeSeries_DateFormatter":
543543
try:
544544
dates = matplotlib.dates.epoch2num([date * 24 * 60 * 60 for date in dates])
545-
dates = matplotlib.dates.num2date(dates, tz=pytz.utc)
545+
dates = matplotlib.dates.num2date(dates)
546546
except:
547547
return _dates
548548

@@ -551,7 +551,7 @@ def mpl_dates_to_datestrings(dates, mpl_formatter):
551551
# according to mpl --> try num2date(1)
552552
else:
553553
try:
554-
dates = matplotlib.dates.num2date(dates, tz=pytz.utc)
554+
dates = matplotlib.dates.num2date(dates)
555555
except:
556556
return _dates
557557

Diff for: packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py

+1-96
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import json as _json
77

8-
from plotly.utils import PlotlyJSONEncoder, get_by_path, memoize, node_generator
8+
from plotly.utils import PlotlyJSONEncoder, get_by_path, node_generator
99

1010

1111
class TestJSONEncoder(TestCase):
@@ -48,98 +48,3 @@ def test_node_generator(self):
4848
]
4949
for i, item in enumerate(node_generator(node0)):
5050
self.assertEqual(item, expected_node_path_tuples[i])
51-
52-
53-
class TestMemoizeDecorator(TestCase):
54-
55-
# In Python 2.x, globals should be module-scoped. By defining and
56-
# instantiating a class, we *access* the global first before attempting
57-
# to update a value. I.e., you *cannot* simply mutate the global value
58-
# on it's own.
59-
class Namespace(object):
60-
pass
61-
62-
def test_memoize(self):
63-
name_space = self.Namespace()
64-
name_space.call_count = 0
65-
66-
@memoize()
67-
def add(a, b):
68-
name_space.call_count += 1
69-
return a + b
70-
71-
tests = [[(1, 1), 2], [(2, 3), 5], [(3, -3), 0]]
72-
73-
self.assertEqual(name_space.call_count, 0)
74-
for i, (inputs, result) in enumerate(tests, 1):
75-
for _ in range(10):
76-
self.assertEqual(add(*inputs), result)
77-
self.assertEqual(name_space.call_count, i)
78-
79-
def test_memoize_maxsize(self):
80-
name_space = self.Namespace()
81-
name_space.call_count = 0
82-
83-
maxsize = 10
84-
85-
@memoize(maxsize=maxsize)
86-
def identity(a):
87-
name_space.call_count += 1
88-
return a
89-
90-
# Function hasn't been called yet, we should get *up to* maxsize cache.
91-
for i in range(maxsize):
92-
self.assertEqual(identity(i), i)
93-
self.assertEqual(name_space.call_count, i + 1)
94-
95-
# Nothing should have been discarded yet. no additional calls.
96-
for i in range(maxsize):
97-
self.assertEqual(identity(i), i)
98-
self.assertEqual(name_space.call_count, maxsize)
99-
100-
# Make a new call...
101-
self.assertEqual(identity(maxsize), maxsize)
102-
self.assertEqual(name_space.call_count, maxsize + 1)
103-
104-
# All but the first call should be remembered.
105-
for i in range(1, maxsize + 1):
106-
self.assertEqual(identity(i), i)
107-
self.assertEqual(name_space.call_count, maxsize + 1)
108-
109-
# The *initial* call should now be forgotten for each new call.
110-
for i in range(maxsize):
111-
self.assertEqual(identity(i), i)
112-
self.assertEqual(name_space.call_count, maxsize + 1 + i + 1)
113-
114-
def test_memoize_maxsize_none(self):
115-
name_space = self.Namespace()
116-
name_space.call_count = 0
117-
118-
@memoize(maxsize=None)
119-
def identity(a):
120-
name_space.call_count += 1
121-
return a
122-
123-
# Function hasn't been called yet, we should get *up to* maxsize cache.
124-
for i in range(400):
125-
self.assertEqual(identity(i), i)
126-
self.assertEqual(name_space.call_count, i + 1)
127-
128-
# Nothing should have been discarded. no additional calls.
129-
for i in range(400):
130-
self.assertEqual(identity(i), i)
131-
self.assertEqual(name_space.call_count, 400)
132-
133-
def test_memoize_function_info(self):
134-
# We use the decorator module to assure that function info is not
135-
# overwritten by the decorator.
136-
137-
@memoize()
138-
def foo(a, b, c="see?"):
139-
"""Foo is foo."""
140-
pass
141-
142-
self.assertEqual(foo.__doc__, "Foo is foo.")
143-
self.assertEqual(foo.__name__, "foo")
144-
self.assertEqual(getargspec(foo).args, ["a", "b", "c"])
145-
self.assertEqual(getargspec(foo).defaults, ("see?",))

Diff for: packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py

+4-14
Original file line numberDiff line numberDiff line change
@@ -113,33 +113,23 @@ def test_encode_as_numpy(self):
113113
res = utils.PlotlyJSONEncoder.encode_as_numpy(np.ma.core.masked)
114114
self.assertTrue(math.isnan(res))
115115

116-
def test_encode_valid_datetime(self):
117-
118-
# should *fail* without 'utcoffset' and 'isoformat' and '__sub__' attrs
119-
# non_datetimes = [datetime.date(2013, 10, 1), 'noon', 56, '00:00:00']
120-
non_datetimes = [datetime.date(2013, 10, 1)]
121-
for obj in non_datetimes:
122-
self.assertRaises(
123-
utils.NotEncodable, utils.PlotlyJSONEncoder.encode_as_datetime, obj
124-
)
125-
126116
def test_encode_as_datetime(self):
127117
# should succeed with 'utcoffset', 'isoformat' and '__sub__' attrs
128118
res = utils.PlotlyJSONEncoder.encode_as_datetime(datetime.datetime(2013, 10, 1))
129-
self.assertEqual(res, "2013-10-01")
119+
self.assertEqual(res, "2013-10-01T00:00:00")
130120

131121
def test_encode_as_datetime_with_microsecond(self):
132122
# should not include extraneous microsecond info if DNE
133123
res = utils.PlotlyJSONEncoder.encode_as_datetime(
134124
datetime.datetime(2013, 10, 1, microsecond=0)
135125
)
136-
self.assertEqual(res, "2013-10-01")
126+
self.assertEqual(res, "2013-10-01T00:00:00")
137127

138128
# should include microsecond info if present
139129
res = utils.PlotlyJSONEncoder.encode_as_datetime(
140130
datetime.datetime(2013, 10, 1, microsecond=10)
141131
)
142-
self.assertEqual(res, "2013-10-01 00:00:00.000010")
132+
self.assertEqual(res, "2013-10-01T00:00:00.000010")
143133

144134
def test_encode_as_datetime_with_localized_tz(self):
145135
# 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):
148138
aware_datetime = pytz.timezone("US/Eastern").localize(naive_datetime)
149139

150140
res = utils.PlotlyJSONEncoder.encode_as_datetime(aware_datetime)
151-
self.assertEqual(res, "2013-10-01 04:00:00")
141+
self.assertEqual(res, "2013-10-01T00:00:00-04:00")
152142

153143
def test_encode_as_date(self):
154144

Diff for: packages/python/plotly/plotly/utils.py

-46
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
from __future__ import absolute_import, division
22

33
import textwrap
4-
from collections import deque
54
from pprint import PrettyPrinter
65

7-
from decorator import decorator
86
from _plotly_utils.utils import *
97

108

@@ -209,47 +207,3 @@ def decode_unicode(coll):
209207
pass
210208
coll[str(key)] = coll.pop(key)
211209
return coll
212-
213-
214-
def memoize(maxsize=128):
215-
"""
216-
Memoize a function by its arguments. Note, if the wrapped function returns
217-
a mutable result, the caller is responsible for *not* mutating the result
218-
as it will mutate the cache itself.
219-
220-
:param (int|None) maxsize: Limit the number of cached results. This is a
221-
simple way to prevent memory leaks. Setting this
222-
to `None` will remember *all* calls. The 128
223-
number is used for parity with the Python 3.2
224-
`functools.lru_cache` tool.
225-
226-
"""
227-
keys = deque()
228-
cache = {}
229-
230-
def _memoize(*all_args, **kwargs):
231-
func = all_args[0]
232-
args = all_args[1:]
233-
key = _default_memoize_key_function(*args, **kwargs)
234-
235-
if key in keys:
236-
return cache[key]
237-
238-
if maxsize is not None and len(keys) == maxsize:
239-
cache.pop(keys.pop())
240-
241-
result = func(*args, **kwargs)
242-
keys.appendleft(key)
243-
cache[key] = result
244-
return result
245-
246-
return decorator(_memoize)
247-
248-
249-
def _default_memoize_key_function(*args, **kwargs):
250-
"""Factored out in case we want to allow callers to specify this func."""
251-
if kwargs:
252-
# frozenset is used to ensure hashability
253-
return args, frozenset(kwargs.items())
254-
else:
255-
return args

Diff for: packages/python/plotly/recipe/meta.yaml

+1-5
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@ requirements:
1818
- python
1919
- pip
2020
- nodejs
21+
- setuptools
2122
run:
2223
- python
23-
- setuptools
24-
- decorator >=4.0.6
25-
- nbformat >=4.2
26-
- pytz
27-
- requests
2824
- retrying >=1.3.3
2925
- six
3026

0 commit comments

Comments
 (0)