From ea05235ddcb8d2cb0142842e7c9bf3ca19e3b4b9 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Thu, 3 Dec 2015 16:26:55 -0800 Subject: [PATCH 1/3] Fixed crash when opening links from the command line. --- rtv/__main__.py | 18 +++++++++++------- rtv/subreddit.py | 5 +---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/rtv/__main__.py b/rtv/__main__.py index 44d849e..2125185 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -65,10 +65,9 @@ def main(): with curses_session() as stdscr: term = Terminal(stdscr, config['ascii']) with term.loader(catch_exception=False): - reddit = praw.Reddit( - user_agent=user_agent, - decode_html_entities=False, - disable_update_check=True) + reddit = praw.Reddit(user_agent=user_agent, + decode_html_entities=False, + disable_update_check=True) # Authorize on launch if the refresh token is present oauth = OAuthHelper(reddit, term, config) @@ -76,13 +75,18 @@ def main(): oauth.authorize() with term.loader(): - page = SubredditPage( - reddit, term, config, oauth, - name=config['subreddit'], url=config['link']) + page = SubredditPage(reddit, term, config, oauth, + config['subreddit']) if term.loader.exception: return + # Open the supplied submission link before opening the subreddit + if config['link']: + page.open_submission(url=config['link']) + + # Launch the subreddit page page.loop() + except Exception as e: _logger.exception(e) raise diff --git a/rtv/subreddit.py b/rtv/subreddit.py index b010ad9..cb95ef3 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -19,7 +19,7 @@ class SubredditController(PageController): class SubredditPage(Page): - def __init__(self, reddit, term, config, oauth, name, url=None): + def __init__(self, reddit, term, config, oauth, name): """ Params: name (string): Name of subreddit to open @@ -31,9 +31,6 @@ class SubredditPage(Page): self.controller = SubredditController(self) self.nav = Navigator(self.content.get) - if url: - self.open_submission(url=url) - @SubredditController.register(curses.KEY_F5, 'r') def refresh_content(self, name=None, order=None): "Re-download all submissions and reset the page index" From 9c6d3ab542f61c565bb4f2edd15fd6df190e7d09 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Thu, 3 Dec 2015 18:15:53 -0800 Subject: [PATCH 2/3] Generalized exception handling to handle strange errors like captchas. --- rtv/content.py | 6 ++--- rtv/objects.py | 59 ++++++++++++++++++++---------------------------- rtv/subreddit.py | 3 ++- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 1713fbc..9dd8015 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -339,7 +339,7 @@ class SubredditContent(Content): try: self.get(0) except IndexError: - raise exceptions.SubredditError('Unable to retrieve subreddit') + raise exceptions.SubredditError('No submissions') @classmethod def from_name(cls, reddit, name, loader, order=None, query=None): @@ -361,7 +361,7 @@ class SubredditContent(Content): if name == 'me': if not reddit.is_oauth_session(): - raise exceptions.AccountError('Could not access user account') + raise exceptions.AccountError('Not logged in') elif order: submissions = reddit.user.get_submitted(sort=order) else: @@ -443,7 +443,7 @@ class SubscriptionContent(Content): try: self.get(0) except IndexError: - raise exceptions.SubscriptionError('Unable to load subscriptions') + raise exceptions.SubscriptionError('No subscriptions') @classmethod def from_user(cls, reddit, loader): diff --git a/rtv/objects.py b/rtv/objects.py index c4b419f..50ac828 100644 --- a/rtv/objects.py +++ b/rtv/objects.py @@ -12,10 +12,10 @@ import threading from contextlib import contextmanager import six -import praw -import requests +from praw.errors import PRAWException +from requests import RequestException -from . import exceptions +from .exceptions import RTVError _logger = logging.getLogger(__name__) @@ -111,25 +111,6 @@ class LoadScreen(object): >>> assert isinstance(terminal.loader.exception, KeyboardInterrupt) """ - HANDLED_EXCEPTIONS = [ - (exceptions.SubscriptionError, 'No Subscriptions'), - (exceptions.AccountError, 'Unable to Access Account'), - (exceptions.SubredditError, 'Invalid Subreddit'), - (praw.errors.InvalidSubreddit, 'Invalid Subreddit'), - (praw.errors.InvalidComment, 'Invalid Comment'), - (praw.errors.InvalidSubmission, 'Invalid Submission'), - (praw.errors.OAuthAppRequired, 'Invalid OAuth data'), - (praw.errors.OAuthException, 'Invalid OAuth data'), - (praw.errors.LoginOrScopeRequired, 'Not Logged In'), - (praw.errors.ClientException, 'Reddit Client Error'), - (praw.errors.NotFound, 'Not Found'), - (praw.errors.APIException, 'Reddit API Error'), - (praw.errors.HTTPException, 'Reddit HTTP Error'), - (requests.HTTPError, 'Unexpected HTTP Error'), - (requests.ConnectionError, 'Connection Error'), - (KeyboardInterrupt, None), - ] - def __init__(self, terminal): self.exception = None @@ -186,19 +167,27 @@ class LoadScreen(object): self._animator.join() self._terminal.stdscr.refresh() - if self.catch_exception and e is not None: - # Log the exception and attach it so the caller can inspect it - self.exception = e - _logger.info('Loader caught: {0} - {1}'.format(type(e).__name__, e)) - # If an error occurred, display a notification on the screen - for base, message in self.HANDLED_EXCEPTIONS: - if isinstance(e, base): - if message: - self._terminal.show_notification(message) - break - else: - return # Re-raise unhandled exceptions - return True # Otherwise swallow the exception and continue + if e is None or not self.catch_exception: + # Skip exception handling + return + + self.exception = e + exc_name = type(e).__name__ + _logger.info('Loader caught: {0} - {1}'.format(exc_name, e)) + + # Some exceptions we want to swallow and display a notification + handled_exceptions = (RTVError, PRAWException, RequestException) + if isinstance(e, handled_exceptions): + # Pass the message straight through to the user + message = six.text_type(e).split('/n') if e else exc_name + self._terminal.show_notification(message) + return True + elif isinstance(e, KeyboardInterrupt): + # Don't need to print anything for this one + return True + else: + # Allow the exception to re-raise + return None def animate(self, delay, interval, message, trail): diff --git a/rtv/subreddit.py b/rtv/subreddit.py index cb95ef3..c82aecd 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -124,7 +124,8 @@ class SubredditPage(Page): title, content = text.split('\n', 1) with self.term.loader(message='Posting', delay=0): - submission = self.reddit.submit(name, title, text=content) + submission = self.reddit.submit(name, title, text=content, + raise_captcha_exception=True) # Give reddit time to process the submission time.sleep(2.0) if self.term.loader.exception: From 7ebcfb9cca36dd2bcddb2577af9a1e77b0193e26 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Thu, 3 Dec 2015 20:55:37 -0800 Subject: [PATCH 3/3] Updated loader tests. --- rtv/objects.py | 2 +- tests/test_oauth.py | 6 ++---- tests/test_objects.py | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/rtv/objects.py b/rtv/objects.py index 50ac828..3bbf274 100644 --- a/rtv/objects.py +++ b/rtv/objects.py @@ -179,7 +179,7 @@ class LoadScreen(object): handled_exceptions = (RTVError, PRAWException, RequestException) if isinstance(e, handled_exceptions): # Pass the message straight through to the user - message = six.text_type(e).split('/n') if e else exc_name + message = six.text_type(e).split('/n') if str(e) else exc_name self._terminal.show_notification(message) return True elif isinstance(e, KeyboardInterrupt): diff --git a/tests/test_oauth.py b/tests/test_oauth.py index ac072cb..25332dc 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -77,8 +77,7 @@ def test_oauth_authorize_with_refresh_token(oauth, stdscr, refresh_token): exception = OAuthException('', '') oauth.reddit.refresh_access_information.side_effect = exception oauth.authorize() - message = 'Invalid OAuth data'.encode('utf-8') - stdscr.derwin().addstr.assert_called_with(1, 1, message) + assert isinstance(oauth.term.loader.exception, OAuthException) assert oauth.http_server is None @@ -149,8 +148,7 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token): exception = OAuthException('', '') oauth.reddit.get_access_information.side_effect = exception oauth.authorize() - message = 'Invalid OAuth data'.encode('utf-8') - stdscr.derwin().addstr.assert_called_with(1, 1, message) + assert isinstance(oauth.term.loader.exception, OAuthException) assert not oauth.config.save_refresh_token.called diff --git a/tests/test_objects.py b/tests/test_objects.py index f0aad55..2dda79e 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -53,7 +53,7 @@ def test_objects_load_screen_exception_handled(terminal, stdscr, ascii): assert not terminal.loader._is_running assert not terminal.loader._animator.is_alive() assert isinstance(terminal.loader.exception, requests.ConnectionError) - error_message = 'Connection Error'.encode('ascii' if ascii else 'utf-8') + error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8') stdscr.subwin.addstr.assert_called_with(1, 1, error_message) @@ -152,7 +152,7 @@ def test_objects_load_screen_nested_complex(terminal, stdscr, ascii): assert terminal.loader.depth == 0 assert not terminal.loader._is_running assert not terminal.loader._animator.is_alive() - error_message = 'Connection Error'.encode('ascii' if ascii else 'utf-8') + error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8') stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)