Skip to content
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

Add GlobusSDKUsageError & replace most ValueErrors #281

Merged
merged 1 commit into from
Feb 23, 2018
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
30 changes: 18 additions & 12 deletions docs/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ You can therefore capture *all* errors thrown by the SDK by looking for
from globus_sdk import TransferClient, GlobusError

try:
tc = TransferClient()
tc = TransferClient(...)
# search with no parameters will throw an exception
eps = tc.endpoint_search()
except exc.GlobusError:
except GlobusError:
logging.exception("Globus Error!")
raise

Expand All @@ -28,7 +28,7 @@ unexpected API conditions, you'll want to look for ``NetworkError`` and
GlobusError, GlobusAPIError, NetworkError)

try:
tc = TransferClient()
tc = TransferClient(...)

eps = tc.endpoint_search(filter_fulltext="myendpointsearch")

Expand All @@ -55,34 +55,40 @@ unexpected API conditions, you'll want to look for ``NetworkError`` and

Of course, if you want to learn more information about the response, you should
inspect it more than this.
Malformed calls to Globus SDK methods may raise standard python exceptions
(``ValueError``, etc.), but for correct usage, all errors will be instances of
``GlobusError``.

All errors raised by the SDK should be instances of ``GlobusError``.
Malformed calls to Globus SDK methods typically raise ``GlobusSDKUsageError``,
but, in rare cases, may raise standard python exceptions (``ValueError``,
``OSError``, etc.)


Error Classes
-------------

.. autoclass:: globus_sdk.exc.GlobusError
.. autoclass:: globus_sdk.GlobusError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.GlobusSDKUsageError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.exc.GlobusAPIError
.. autoclass:: globus_sdk.GlobusAPIError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.exc.NetworkError
.. autoclass:: globus_sdk.NetworkError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.exc.GlobusConnectionError
.. autoclass:: globus_sdk.GlobusConnectionError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.exc.GlobusTimeoutError
.. autoclass:: globus_sdk.GlobusTimeoutError
:members:
:show-inheritance:

.. autoclass:: globus_sdk.exc.GlobusConnectionTimeoutError
.. autoclass:: globus_sdk.GlobusConnectionTimeoutError
:members:
:show-inheritance:
11 changes: 7 additions & 4 deletions docs/oauth/flows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ Most typically, you should not use these objects, but rather rely on the
:class:`globus_sdk.AuthClient` object to manage one of these for you through
its ``oauth2_*`` methods.

All Flow Managers inherit from the
All Flow Managers inherit from the
:class:`GlobusOAuthFlowManager \
<globus_sdk.auth.oauth_flow_manager.GlobusOAuthFlowManager>` abstract class.
They are a combination of a store for OAuth2 parameters specific to the
authentication method you are using and methods which act upon those parameters.

.. autoclass:: globus_sdk.auth.oauth_flow_manager.GlobusOAuthFlowManager
.. autoclass:: globus_sdk.auth.GlobusNativeAppFlowManager
:members:
:show-inheritance:

.. autoclass:: globus_sdk.auth.GlobusNativeAppFlowManager
.. autoclass:: globus_sdk.auth.GlobusAuthorizationCodeFlowManager
:members:
:show-inheritance:

.. autoclass:: globus_sdk.auth.GlobusAuthorizationCodeFlowManager
Abstract Flow Manager
~~~~~~~~~~~~~~~~~~~~~

.. autoclass:: globus_sdk.auth.oauth2_flow_manager.GlobusOAuthFlowManager
:members:
:show-inheritance:
6 changes: 4 additions & 2 deletions globus_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from globus_sdk.response import GlobusResponse, GlobusHTTPResponse

from globus_sdk.exc import (
GlobusError, GlobusAPIError, TransferAPIError, SearchAPIError,
GlobusError, GlobusSDKUsageError,
GlobusAPIError, TransferAPIError, SearchAPIError,
NetworkError, GlobusConnectionError, GlobusTimeoutError,
GlobusConnectionTimeoutError)

Expand All @@ -30,7 +31,8 @@

"GlobusResponse", "GlobusHTTPResponse",

"GlobusError", "GlobusAPIError", "TransferAPIError", "SearchAPIError",
"GlobusError", "GlobusSDKUsageError",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to introduce this new error class in docs/exceptions.rst as well, including maybe rephrasing the "malformed calls to Globus SDK methods may raise standard python exceptions" bit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think a doc update would be appropriate. Will add.

"GlobusAPIError", "TransferAPIError", "SearchAPIError",
"NetworkError", "GlobusConnectionError", "GlobusTimeoutError",
"GlobusConnectionTimeoutError",

Expand Down
4 changes: 2 additions & 2 deletions globus_sdk/auth/client_types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def oauth2_get_authorize_url(self, additional_params=None):
if not self.current_oauth2_flow_manager:
self.logger.error(('OutOfOrderOperations('
'get_authorize_url before start_flow)'))
raise ValueError(
raise exc.GlobusSDKUsageError(
('Cannot get authorize URL until starting an OAuth2 flow. '
'Call the oauth2_start_flow() method on this '
'AuthClient to resolve'))
Expand All @@ -189,7 +189,7 @@ def oauth2_exchange_code_for_tokens(self, auth_code):
if not self.current_oauth2_flow_manager:
self.logger.error(('OutOfOrderOperations('
'exchange_code before start_flow)'))
raise ValueError(
raise exc.GlobusSDKUsageError(
('Cannot exchange auth code until starting an OAuth2 flow. '
'Call the oauth2_start_flow() method on this '
'AuthClient to resolve'))
Expand Down
3 changes: 2 additions & 1 deletion globus_sdk/auth/client_types/confidential_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import six

from globus_sdk.exc import GlobusSDKUsageError
from globus_sdk.base import merge_params
from globus_sdk.authorizers import BasicAuthorizer
from globus_sdk.auth.oauth2_constants import DEFAULT_REQUESTED_SCOPES
Expand Down Expand Up @@ -37,7 +38,7 @@ class ConfidentialAppAuthClient(AuthClient):
def __init__(self, client_id, client_secret, **kwargs):
if "authorizer" in kwargs:
logger.error('ArgumentError(ConfidentialAppClient.authorizer)')
raise ValueError(
raise GlobusSDKUsageError(
"Cannot give a ConfidentialAppAuthClient an authorizer")

AuthClient.__init__(
Expand Down
4 changes: 3 additions & 1 deletion globus_sdk/auth/client_types/native_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging

from globus_sdk.exc import GlobusSDKUsageError
from globus_sdk.authorizers import NullAuthorizer
from globus_sdk.auth.client_types.base import AuthClient
from globus_sdk.auth.oauth2_native_app import GlobusNativeAppFlowManager
Expand Down Expand Up @@ -27,7 +29,7 @@ class NativeAppAuthClient(AuthClient):
def __init__(self, client_id, **kwargs):
if "authorizer" in kwargs:
logger.error('ArgumentError(NativeAppClient.authorizer)')
raise ValueError(
raise GlobusSDKUsageError(
"Cannot give a NativeAppAuthClient an authorizer")

AuthClient.__init__(
Expand Down
7 changes: 4 additions & 3 deletions globus_sdk/auth/oauth2_native_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import six
from six.moves.urllib.parse import urlencode

from globus_sdk.exc import GlobusSDKUsageError
from globus_sdk.base import slash_join
from globus_sdk.auth.oauth2_constants import DEFAULT_REQUESTED_SCOPES
from globus_sdk.auth.oauth2_flow_manager import GlobusOAuthFlowManager
Expand Down Expand Up @@ -35,11 +36,11 @@ def make_native_app_challenge(verifier=None):

if verifier:
if not 43 <= len(verifier) <= 128:
raise ValueError(
raise GlobusSDKUsageError(
'verifier must be 43-128 characters long: {}'.format(
len(verifier)))
if bool(re.search(r'[^a-zA-Z0-9~_.-]', verifier)):
raise ValueError('verifier contained invalid characters')
raise GlobusSDKUsageError('verifier contained invalid characters')
else:
logger.info(('Autogenerating verifier secret. On low-entropy systems '
'this may be insecure'))
Expand Down Expand Up @@ -114,7 +115,7 @@ def __init__(self, auth_client, requested_scopes=None,
if not self.client_id:
logger.error('Invalid auth_client ID to start Native App Flow: {}'
.format(self.client_id))
raise ValueError(
raise GlobusSDKUsageError(
'Invalid value for client_id. Got "{0}"'
.format(self.client_id))

Expand Down
2 changes: 1 addition & 1 deletion globus_sdk/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(self, service, environment=None, base_url=None,
type(authorizer) not in self.allowed_authorizer_types):
self.logger.error("{} doesn't support authorizer={}"
.format(type(self), type(authorizer)))
raise ValueError(
raise exc.GlobusSDKUsageError(
("{0} can only take authorizers from {1}, "
"but you have provided {2}").format(
type(self), self.allowed_authorizer_types,
Expand Down
4 changes: 2 additions & 2 deletions globus_sdk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
ConfigParser, MissingSectionHeaderError,
NoOptionError, NoSectionError)

from globus_sdk.exc import GlobusError
from globus_sdk.exc import GlobusError, GlobusSDKUsageError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -133,7 +133,7 @@ def get_service_url(environment, service):
# TODO: validate with urlparse?
url = p.get(option, environment=environment)
if url is None:
raise ValueError(
raise GlobusSDKUsageError(
('Failed to find a url for service "{}" in environment "{}". '
"Please double-check that GLOBUS_SDK_ENVIRONMENT is set "
"correctly, or not set at all")
Expand Down
10 changes: 10 additions & 0 deletions globus_sdk/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ class GlobusError(Exception):
"""


class GlobusSDKUsageError(GlobusError, ValueError):
"""
A ``GlobusSDKUsageError`` may be thrown in cases in which the SDK
detects that it is being used improperly.

These errors typically indicate that some contract regarding SDK usage
(e.g. required order of operations) has been violated.
"""


class GlobusAPIError(GlobusError):
"""
Wraps errors returned by a REST API.
Expand Down
4 changes: 3 additions & 1 deletion globus_sdk/local_endpoint/personal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

from globus_sdk.exc import GlobusSDKUsageError


def _on_windows():
"""
Expand Down Expand Up @@ -49,7 +51,7 @@ def endpoint_id(self):
if _on_windows():
appdata = os.getenv("LOCALAPPDATA")
if appdata is None:
raise ValueError(
raise GlobusSDKUsageError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the absence of LOCALAPPDATA on a Windows system the fault of usage, or is it the fault of a broken Windows installation? I don't think it matters, and we should probably leave it alone as it would be a breaking change, but maybe an OSError would have been better here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering it a usage error from the start because it's not okay to try to use this method after clearing os.environ or invoking python with a really messed up env in the first place.
It's not possible for us to know why it wasn't set. So I just assume that it's the caller's fault at some level! 😄

I doubt this will ever get tripped, but #robustnessprinciple

Anyway, it sounds like you're okay with this error as it is in this PR.

"LOCALAPPDATA not detected in Windows environment")
fname = os.path.join(
appdata, "Globus Connect\client-id.txt")
Expand Down
18 changes: 10 additions & 8 deletions globus_sdk/transfer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ def update_endpoint(self, endpoint_id, data, **params):
"""
if data.get('myproxy_server'):
if data.get('oauth_server'):
raise ValueError("an endpoint cannot be reconfigured to use "
"multiple identity providers for activation; "
"specify either MyProxy or OAuth, not both")
raise exc.GlobusSDKUsageError(
"an endpoint cannot be reconfigured to use multiple "
"identity providers for activation; specify either "
"MyProxy or OAuth, not both")
else:
data['oauth_server'] = None
elif data.get('oauth_server'):
Expand Down Expand Up @@ -159,9 +160,10 @@ def create_endpoint(self, data):
in the REST documentation for details.
"""
if data.get('myproxy_server') and data.get('oauth_server'):
raise ValueError("an endpoint cannot be created using multiple "
"identity providers for activation; "
"specify either MyProxy or OAuth, not both")
raise exc.GlobusSDKUsageError(
"an endpoint cannot be created using multiple identity "
"providers for activation; specify either MyProxy or OAuth, "
"not both")

self.logger.info("TransferClient.create_endpoint(...)")
return self.post("endpoint", data)
Expand Down Expand Up @@ -1333,13 +1335,13 @@ def task_wait(self, task_id, timeout=10, polling_interval=10):
self.logger.error(
"task_wait() timeout={} is less than minimum of 1s"
.format(timeout))
raise ValueError(
raise exc.GlobusSDKUsageError(
"TransferClient.task_wait timeout has a minimum of 1")
if polling_interval < 1:
self.logger.error(
"task_wait() polling_interval={} is less than minimum of 1s"
.format(polling_interval))
raise ValueError(
raise exc.GlobusSDKUsageError(
"TransferClient.task_wait polling_interval has a minimum of 1")

# ensure that we always wait at least one interval, even if the timeout
Expand Down
3 changes: 2 additions & 1 deletion globus_sdk/transfer/paging.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import six

from globus_sdk.exc import GlobusSDKUsageError
from globus_sdk.response import GlobusResponse
from globus_sdk.transfer.response import IterableTransferResponse

Expand Down Expand Up @@ -289,7 +290,7 @@ def _check_has_next_page(res):

logger.error("PaginatedResource.paging_style={} is invalid"
.format(self.paging_style))
raise ValueError(
raise GlobusSDKUsageError(
'Invalid Paging Style Given to PaginatedResource')

has_next_page = True
Expand Down