Skip to content

V4 refine dependencies #1631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion .circleci/create_conda_optional_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 2 additions & 33 deletions packages/python/plotly/_plotly_utils/utils.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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."""
Expand Down
4 changes: 4 additions & 0 deletions packages/python/plotly/optional-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ nose==1.3.3
pytest==3.5.1
backports.tempfile==1.0
xarray
pytz

## orca dependencies ##
requests
psutil

## code formatting
Expand Down
14 changes: 14 additions & 0 deletions packages/python/plotly/plotly/io/_orca.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 7 additions & 0 deletions packages/python/plotly/plotly/io/_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import six
import os
from distutils.version import LooseVersion

from plotly import optional_imports

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions packages/python/plotly/plotly/matplotlylib/mpltools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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?",))
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand All @@ -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):

Expand Down
46 changes: 0 additions & 46 deletions packages/python/plotly/plotly/utils.py
Original file line number Diff line number Diff line change
@@ -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 *


Expand Down Expand Up @@ -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
6 changes: 1 addition & 5 deletions packages/python/plotly/recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading