From b25809ae3d57b76dbc6d9f85edf51363771a721d Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Tue, 17 Jan 2017 16:54:38 -0800 Subject: [PATCH 1/3] Namespace `session` import in `plotly.py`. --- plotly/plotly/plotly.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plotly/plotly/plotly.py b/plotly/plotly/plotly.py index bd1191fb30d..d9d1def47f0 100644 --- a/plotly/plotly/plotly.py +++ b/plotly/plotly/plotly.py @@ -24,11 +24,9 @@ import six.moves from requests.compat import json as _json -from plotly import exceptions, tools, utils, files +from plotly import exceptions, files, session, tools, utils from plotly.api import v1, v2 from plotly.plotly import chunked_requests -from plotly.session import (sign_in, update_session_plot_options, - get_session_plot_options) from plotly.grid_objs import Grid, Column # This is imported like this for backwards compat. Careful if changing. @@ -50,8 +48,8 @@ # don't break backwards compatibility -sign_in = sign_in -update_plot_options = update_session_plot_options +sign_in = session.sign_in +update_plot_options = session.update_session_plot_options def _plot_option_logic(plot_options_from_call_signature): @@ -66,7 +64,7 @@ def _plot_option_logic(plot_options_from_call_signature): """ default_plot_options = copy.deepcopy(DEFAULT_PLOT_OPTIONS) file_options = tools.get_config_file() - session_options = get_session_plot_options() + session_options = session.get_session_plot_options() plot_options_from_call_signature = copy.deepcopy(plot_options_from_call_signature) # Validate options and fill in defaults w world_readable and sharing From a7cfc2515cd11c6a037c7804237a88ff34670eda Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Tue, 17 Jan 2017 18:28:07 -0800 Subject: [PATCH 2/3] Add `users.current` endpoint to v2 api. --- plotly/api/v2/__init__.py | 3 +- plotly/api/v2/users.py | 17 +++++++++++ .../test_core/test_api/test_v2/test_users.py | 28 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 plotly/api/v2/users.py create mode 100644 plotly/tests/test_core/test_api/test_v2/test_users.py diff --git a/plotly/api/v2/__init__.py b/plotly/api/v2/__init__.py index 95c8a84e4d3..8424927d1c6 100644 --- a/plotly/api/v2/__init__.py +++ b/plotly/api/v2/__init__.py @@ -1,3 +1,4 @@ from __future__ import absolute_import -from plotly.api.v2 import files, folders, grids, images, plot_schema, plots +from plotly.api.v2 import (files, folders, grids, images, plot_schema, plots, + users) diff --git a/plotly/api/v2/users.py b/plotly/api/v2/users.py new file mode 100644 index 00000000000..cdfaf51c488 --- /dev/null +++ b/plotly/api/v2/users.py @@ -0,0 +1,17 @@ +"""Interface to Plotly's /v2/files endpoints.""" +from __future__ import absolute_import + +from plotly.api.v2.utils import build_url, request + +RESOURCE = 'users' + + +def current(): + """ + Retrieve information on the logged-in user from Plotly. + + :returns: (requests.Response) Returns response directly from requests. + + """ + url = build_url(RESOURCE, route='current') + return request('get', url) diff --git a/plotly/tests/test_core/test_api/test_v2/test_users.py b/plotly/tests/test_core/test_api/test_v2/test_users.py new file mode 100644 index 00000000000..59cf8731d56 --- /dev/null +++ b/plotly/tests/test_core/test_api/test_v2/test_users.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import + +from plotly.api.v2 import users +from plotly.tests.test_core.test_api import PlotlyApiTestCase + + +class UsersTest(PlotlyApiTestCase): + + def setUp(self): + super(UsersTest, self).setUp() + + # Mock the actual api call, we don't want to do network tests here. + self.request_mock = self.mock('plotly.api.v2.utils.requests.request') + self.request_mock.return_value = self.get_response() + + # Mock the validation function since we can test that elsewhere. + self.mock('plotly.api.v2.utils.validate_response') + + def test_current(self): + users.current() + self.request_mock.assert_called_once() + args, kwargs = self.request_mock.call_args + method, url = args + self.assertEqual(method, 'get') + self.assertEqual( + url, '{}/v2/users/current'.format(self.plotly_api_domain) + ) + self.assertNotIn('params', kwargs) From 279a6481a7044b7d27c7cba4a672c080c7183882 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Tue, 17 Jan 2017 18:28:27 -0800 Subject: [PATCH 3/3] Raise `PlotlyError` if sign_in fails. --- CHANGELOG.md | 2 + plotly/plotly/plotly.py | 10 ++- .../test_core/test_plotly/test_credentials.py | 61 +++++++++++-------- .../tests/test_core/test_plotly/test_plot.py | 9 +++ 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd1449502b..c8c5843c68c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ failures. Previously either a `PlotlyError`, `PlotlyRequestException`, or a `requests.exceptions.ReqestException` could be raised. In particular, scripts which depend on `try-except` blocks containing network requests should be revisited. +- `plotly.py:sign_in` now validates to the plotly server specified in your + config. If it cannot make a successful request, it raises a `PlotlyError`. ### Deprecated - `plotly.tools.FigureFactory`. Use `plotly.figure_factory.*`. diff --git a/plotly/plotly/plotly.py b/plotly/plotly/plotly.py index d9d1def47f0..ffefaff2f0f 100644 --- a/plotly/plotly/plotly.py +++ b/plotly/plotly/plotly.py @@ -48,7 +48,15 @@ # don't break backwards compatibility -sign_in = session.sign_in +def sign_in(username, api_key, **kwargs): + session.sign_in(username, api_key, **kwargs) + try: + # The only way this can succeed is if the user can be authenticated + # with the given, username, api_key, and plotly_api_domain. + v2.users.current() + except exceptions.PlotlyRequestError: + raise exceptions.PlotlyError('Sign in failed.') + update_plot_options = session.update_session_plot_options diff --git a/plotly/tests/test_core/test_plotly/test_credentials.py b/plotly/tests/test_core/test_plotly/test_credentials.py index 573b30c91b0..73b9eca8767 100644 --- a/plotly/tests/test_core/test_plotly/test_credentials.py +++ b/plotly/tests/test_core/test_plotly/test_credentials.py @@ -1,39 +1,43 @@ from __future__ import absolute_import -from unittest import TestCase +from mock import patch import plotly.plotly.plotly as py import plotly.session as session import plotly.tools as tls +from plotly import exceptions +from plotly.tests.utils import PlotlyTestCase -def test_get_credentials(): - session_credentials = session.get_session_credentials() - if 'username' in session_credentials: - del session._session['credentials']['username'] - if 'api_key' in session_credentials: - del session._session['credentials']['api_key'] - creds = py.get_credentials() - file_creds = tls.get_credentials_file() - print(creds) - print(file_creds) - assert creds == file_creds +class TestSignIn(PlotlyTestCase): + def setUp(self): + super(TestSignIn, self).setUp() + patcher = patch('plotly.api.v2.users.current') + self.users_current_mock = patcher.start() + self.addCleanup(patcher.stop) -def test_sign_in(): - un = 'anyone' - ak = 'something' - # TODO, add this! - # si = ['this', 'and-this'] - py.sign_in(un, ak) - creds = py.get_credentials() - assert creds['username'] == un - assert creds['api_key'] == ak - # TODO, and check it! - # assert creds['stream_ids'] == si + def test_get_credentials(self): + session_credentials = session.get_session_credentials() + if 'username' in session_credentials: + del session._session['credentials']['username'] + if 'api_key' in session_credentials: + del session._session['credentials']['api_key'] + creds = py.get_credentials() + file_creds = tls.get_credentials_file() + self.assertEqual(creds, file_creds) - -class TestSignIn(TestCase): + def test_sign_in(self): + un = 'anyone' + ak = 'something' + # TODO, add this! + # si = ['this', 'and-this'] + py.sign_in(un, ak) + creds = py.get_credentials() + self.assertEqual(creds['username'], un) + self.assertEqual(creds['api_key'], ak) + # TODO, and check it! + # assert creds['stream_ids'] == si def test_get_config(self): plotly_domain = 'test domain' @@ -74,3 +78,10 @@ def test_sign_in_with_config(self): self.assertEqual( config['plotly_ssl_verification'], plotly_ssl_verification ) + + def test_sign_in_cannot_validate(self): + self.users_current_mock.side_effect = exceptions.PlotlyRequestError( + 'msg', 400, 'foobar' + ) + with self.assertRaisesRegexp(exceptions.PlotlyError, 'Sign in failed'): + py.sign_in('foo', 'bar') diff --git a/plotly/tests/test_core/test_plotly/test_plot.py b/plotly/tests/test_core/test_plotly/test_plot.py index 77e9c84d6b3..0e3213e646f 100644 --- a/plotly/tests/test_core/test_plotly/test_plot.py +++ b/plotly/tests/test_core/test_plotly/test_plot.py @@ -12,6 +12,7 @@ from requests.compat import json as _json from unittest import TestCase +from mock import patch from nose.plugins.attrib import attr from nose.tools import raises @@ -223,6 +224,14 @@ class TestPlotOptionLogic(PlotlyTestCase): {'world_readable': False, 'sharing': 'public'} ) + def setUp(self): + super(TestPlotOptionLogic, self).setUp() + + # Make sure we don't hit sign-in validation failures. + patcher = patch('plotly.api.v2.users.current') + self.users_current_mock = patcher.start() + self.addCleanup(patcher.stop) + def test_default_options(self): options = py._plot_option_logic({}) config_options = tls.get_config_file()