Example #1
0
def test_tweet(self):
    consumer_key = settings.TWITTER_CONSUMER_KEY
    consumer_secret = settings.TWITTER_CONSUMER_SECRET
    access_key = settings.TWITTER_ACCESS_TOKEN_KEY
    access_secret = settings.TWITTER_ACCESS_TOKEN_SECRET
    if any(not i for i in [
            consumer_key,
            consumer_secret,
            access_key,
            access_secret,
    ]):
        LOG.warning("Twitter API credentials not set!")
        return
    api = ThreadedApi(
        consumer_key=consumer_key,
        consumer_secret=consumer_secret,
        access_token_key=access_key,
        access_token_secret=access_secret,
    )
    message = "This is a test long tweet\r\n{}".format("\r\n".join(
        f"This is line {line}" for line in range(2, 30)))
    try:
        if calc_expected_status_length(message) > CHARACTER_LIMIT:
            api.PostUpdates(message, continuation="…", threaded=True)
        else:
            api.PostUpdate(message)
    except TwitterError as exc:
        LOG.warning("Hit twitter error: %s", exc)
        if str(exc) in RETRYABLE_ERRORS:
            raise self.retry(exc=exc)
        delay = get_twitter_rate_limit_delay(api)
        if delay is None:
            LOG.error("No idea what to do with twitter error %s", exc)
            raise
        raise self.retry(countdown=delay, exc=exc)
Example #2
0
 def setUp(self):
     self.rng = SystemRandom()
     self.api = ThreadedApi()
     self.fake_last_status = None
     # just silence the config check
     # pylint: disable=protected-access
     self.api._config = True
Example #3
0
def tweet_about_beers(self, beer_pks):
    if not beer_pks:
        LOG.warning("nothing to do")
        return
    LOG.debug("Tweeting about beer PKs: %s", beer_pks)
    consumer_key = settings.TWITTER_CONSUMER_KEY
    consumer_secret = settings.TWITTER_CONSUMER_SECRET
    access_key = settings.TWITTER_ACCESS_TOKEN_KEY
    access_secret = settings.TWITTER_ACCESS_TOKEN_SECRET
    if any(not i for i in [
            consumer_key,
            consumer_secret,
            access_key,
            access_secret,
    ]):
        LOG.warning("Twitter API credentials not set!")
        return
    api = ThreadedApi(
        consumer_key=consumer_key,
        consumer_secret=consumer_secret,
        access_token_key=access_key,
        access_token_secret=access_secret,
    )
    # Mark beers which have been removed from the tap list as tweeted about
    Beer.objects.filter(
        tweeted_about=False,
        taps__isnull=True,
    ).update(tweeted_about=True)
    beers = list(
        Beer.objects.filter(
            id__in=beer_pks,
            tweeted_about=False,
        ).select_related(
            "manufacturer",
            "style",
        ).prefetch_related(
            Prefetch(
                "taps",
                queryset=Tap.objects.select_related("venue"),
            ), ).order_by("id"))
    already_tweeted_about = set(i.id for i in Beer.objects.filter(
        tweeted_about=True,
        id__in=beer_pks,
    ))
    unknown_pks = set(beer_pks).difference(already_tweeted_about)
    LOG.debug("Got %s beers", len(beers))
    if not beers:
        if unknown_pks:
            LOG.warning("No beers found! Trying again shortly")
            raise self.retry(countdown=300)
        LOG.info("everything was already tweeted about. No big deal")
        return
    if len(beers) > 10:
        LOG.info("Too many beers to tweet about at once: %s", len(beers))
        beers = beers[:10]
    if len(beers) == 1:
        beer = beers[0]
        if beer.tweeted_about:
            LOG.info("%s has already been tweeted about; skipping.", beer)
            return
        message = format_beer(beer, SINGLE_BEER_TEMPLATE).strip()
        messages = [message]
    else:
        extra_beers = len(beer_pks) - len(beers) - len(already_tweeted_about)
        messages = [
            MULTI_BEER_OUTER.format(
                len(beers),
                "({} still to come!)".format(extra_beers)
                if extra_beers > 0 else "",
            ).strip()
        ] + format_beers(beers)
        if len(messages) == 1:
            LOG.info("All beers already tweeted about")
            return
    message = "\r\n".join(messages)
    LOG.info("Going to tweet: %s", message)

    try:
        if calc_expected_status_length(message) > CHARACTER_LIMIT:
            api.PostUpdates(message, continuation="…", threaded=True)
        else:
            api.PostUpdate(message)
    except TwitterError as exc:
        LOG.warning("Hit twitter error: %s", exc)
        if str(exc) in RETRYABLE_ERRORS:
            raise self.retry(exc=exc)
        LOG.error("Tweet(s) that caused error was %s", message)
        delay = get_twitter_rate_limit_delay(api)
        if delay is None:
            LOG.error("No idea what to do with twitter error %s", exc)
            raise
        raise self.retry(countdown=delay, exc=exc)
    Beer.objects.filter(id__in=[i.id
                                for i in beers]).update(tweeted_about=True)
    LOG.debug("Done tweeting")
Example #4
0
def tweet_about_beers(self, beer_pks):
    if not beer_pks:
        LOG.warning('nothing to do')
        return
    LOG.debug('Tweeting about beer PKs: %s', beer_pks)
    consumer_key = settings.TWITTER_CONSUMER_KEY
    consumer_secret = settings.TWITTER_CONSUMER_SECRET
    access_key = settings.TWITTER_ACCESS_TOKEN_KEY
    access_secret = settings.TWITTER_ACCESS_TOKEN_SECRET
    if any(not i for i in [
            consumer_key,
            consumer_secret,
            access_key,
            access_secret,
    ]):
        LOG.warning('Twitter API credentials not set!')
        return
    api = ThreadedApi(
        consumer_key=consumer_key,
        consumer_secret=consumer_secret,
        access_token_key=access_key,
        access_token_secret=access_secret,
    )
    beers = list(
        Beer.objects.filter(
            id__in=beer_pks,
            tweeted_about=False,
        ).select_related(
            'manufacturer',
            'style',
        ).prefetch_related(
            Prefetch(
                'taps',
                queryset=Tap.objects.select_related('venue'),
            ), ).order_by('id'))
    already_tweeted_about = set(i.id for i in Beer.objects.filter(
        tweeted_about=True,
        id__in=beer_pks,
    ))
    unknown_pks = set(beer_pks).difference(already_tweeted_about)
    LOG.debug('Got %s beers', len(beers))
    if not beers:
        if unknown_pks:
            LOG.warning('No beers found! Trying again shortly')
            raise self.retry(countdown=300)
        LOG.info('everything was already tweeted about. No big deal')
        return
    if len(beers) > 10:
        LOG.info('Too many beers to tweet about at once: %s', len(beers))
        beers = beers[:10]
    if len(beers) == 1:
        beer = beers[0]
        if beer.tweeted_about:
            LOG.info('%s has already been tweeted about; skipping.', beer)
            return
        message = format_beer(beer, SINGLE_BEER_TEMPLATE).strip()
        messages = [message]
    else:
        extra_beers = len(beer_pks) - len(beers) - len(already_tweeted_about)
        messages = [
            MULTI_BEER_OUTER.format(
                len(beers), '({} still to come!)'.format(extra_beers)
                if extra_beers > 0 else '').strip()
        ] + format_beers(beers)
        if len(messages) == 1:
            LOG.info('All beers already tweeted about')
            return
    message = '\r\n'.join(messages)
    LOG.info('Going to tweet: %s', message)

    try:
        if calc_expected_status_length(message) > CHARACTER_LIMIT:
            api.PostUpdates(message, continuation='…', threaded=True)
        else:
            api.PostUpdate(message)
    except TwitterError as exc:
        LOG.warning('Hit twitter error: %s', exc)
        if str(exc) in RETRYABLE_ERRORS:
            raise self.retry(exc=exc)
        delay = get_twitter_rate_limit_delay(api)
        if delay is None:
            LOG.error('No idea what to do with twitter error %s', exc)
            raise
        raise self.retry(countdown=delay, exc=exc)
    Beer.objects.filter(id__in=[i.id
                                for i in beers]).update(tweeted_about=True)
    LOG.debug('Done tweeting')
Example #5
0
class TestThreadedApi(TestCase):
    def setUp(self):
        self.rng = SystemRandom()
        self.api = ThreadedApi()
        self.fake_last_status = None
        # just silence the config check
        # pylint: disable=protected-access
        self.api._config = True

    def test_single_tweet(self):
        msg = 'This is a short message'
        with patch.object(self.api, 'PostUpdate') as mock_post_update:
            self.api.PostUpdates(msg)
            mock_post_update.assert_called_once_with(status=msg)

    def test_long_tweet(self):

        words = [
            ''.join(self.rng.choices(ascii_lowercase, k=25))
            for dummy in range(25)
        ]
        msg = ' '.join(words)

        def get_fake_id(status, in_reply_to_status_id=None):
            if not self.fake_last_status:
                self.assertIsNone(in_reply_to_status_id)
            else:
                self.assertEqual(in_reply_to_status_id,
                                 self.fake_last_status.id)

            self.assertIn(status, msg)
            next_status = FakeStatus()
            self.fake_last_status = next_status
            return next_status

        with patch.object(self.api, 'PostUpdate') as mock_post_update:
            mock_post_update.side_effect = get_fake_id
            self.api.PostUpdates(msg, threaded=True)
            calls = mock_post_update.call_args_list
        # 625 chars + 50 spaces ==> 3 tweets
        self.assertEqual(len(calls), 3)

    def test_long_tweet_no_thread(self):
        words = [
            ''.join(self.rng.choices(ascii_lowercase, k=25))
            for dummy in range(25)
        ]
        msg = ' '.join(words)

        def get_fake_id(status):
            self.assertIn(status, msg)
            next_status = FakeStatus()
            self.fake_last_status = next_status
            return next_status

        with patch.object(self.api, 'PostUpdate') as mock_post_update:
            mock_post_update.side_effect = get_fake_id
            self.api.PostUpdates(msg, threaded=False)
            calls = mock_post_update.call_args_list
        # 625 chars + 50 spaces ==> 3 tweets
        self.assertEqual(len(calls), 3)

    def test_break_tweet_up_by_lines(self):
        tweet = '\r\n'.join([
            'Line 1',
            'Line 2',
            'Line 3',
            # line 4 is 301 chars. Yay.
            'This is an absurdly long line that will become the basis for what'
            ' should be preserved as line 4 but who knows what Twitter will '
            'do. This needs even more filler since Twitter decided to double'
            ' its tweet character limit. I have no idea what else to put in'
            ' here because everything is awful. Enjoy Arby\'s.',
            'Line 5',
        ])
        tweets = self.api.split_tweet_by_lines(
            tweet, character_limit=CHARACTER_LIMIT - len('…'))
        self.assertEqual(len(tweets), 3, tweets)
        self.assertEqual(
            tweets[0],
            'Line 1\r\nLine 2\r\nLine 3',
        )
        self.assertEqual(
            tweets[1], 'This is an absurdly long line that '
            'will become the basis for what'
            ' should be preserved as line 4 but who knows what Twitter will '
            'do. This needs even more filler since Twitter decided to double'
            ' its tweet character limit. I have no idea what else to put in '
            'here because everything')
        self.assertEqual(
            tweets[2],
            'is awful. Enjoy Arby\'s.\r\nLine 5',
        )
class TestThreadedApi(TestCase):
    def setUp(self):
        self.rng = SystemRandom()
        self.api = ThreadedApi()
        self.fake_last_status = None
        # just silence the config check
        # pylint: disable=protected-access
        self.api._config = True

    def test_single_tweet(self):
        msg = "This is a short message"
        with patch.object(self.api, "PostUpdate") as mock_post_update:
            self.api.PostUpdates(msg)
            mock_post_update.assert_called_once_with(status=msg)

    def test_chandlers_ford(self):
        """Test that the Chandlers Ford beers don't cause breakage"""
        split_tweets = self.api.split_tweet_by_lines(REAL_BREAKAGE,
                                                     CHARACTER_LIMIT - 1)
        self.assertEqual(len(split_tweets), 4)
        for tweet in split_tweets:
            self.assertLess(len(tweet), CHARACTER_LIMIT, tweet)

    def test_long_tweet(self):

        words = [
            "".join(self.rng.choices(ascii_lowercase, k=25))
            for dummy in range(25)
        ]
        msg = " ".join(words)

        def get_fake_id(status, in_reply_to_status_id=None):
            if not self.fake_last_status:
                self.assertIsNone(in_reply_to_status_id)
            else:
                self.assertEqual(in_reply_to_status_id,
                                 self.fake_last_status.id)

            self.assertIn(status, msg)
            next_status = FakeStatus()
            self.fake_last_status = next_status
            return next_status

        with patch.object(self.api, "PostUpdate") as mock_post_update:
            mock_post_update.side_effect = get_fake_id
            self.api.PostUpdates(msg, threaded=True)
            calls = mock_post_update.call_args_list
        # 625 chars + 50 spaces ==> 3 tweets
        self.assertEqual(len(calls), 3)

    def test_long_tweet_no_thread(self):
        words = [
            "".join(self.rng.choices(ascii_lowercase, k=25))
            for dummy in range(25)
        ]
        msg = " ".join(words)

        def get_fake_id(status):
            self.assertIn(status, msg)
            next_status = FakeStatus()
            self.fake_last_status = next_status
            return next_status

        with patch.object(self.api, "PostUpdate") as mock_post_update:
            mock_post_update.side_effect = get_fake_id
            self.api.PostUpdates(msg, threaded=False)
            calls = mock_post_update.call_args_list
        # 625 chars + 50 spaces ==> 3 tweets
        self.assertEqual(len(calls), 3)

    def test_break_tweet_up_by_lines(self):
        tweet = "\r\n".join([
            "Line 1",
            "Line 2",
            "Line 3",
            # line 4 is 301 chars. Yay.
            "This is an absurdly long line that will become the basis for what"
            " should be preserved as line 4 but who knows what Twitter will "
            "do. This needs even more filler since Twitter decided to double"
            " its tweet character limit. I have no idea what else to put in"
            " here because everything is awful. Enjoy Arby's.",
            "Line 5",
        ])
        tweets = self.api.split_tweet_by_lines(
            tweet, character_limit=CHARACTER_LIMIT - len("…"))
        self.assertEqual(len(tweets), 3, tweets)
        self.assertEqual(
            tweets[0],
            "Line 1\r\nLine 2\r\nLine 3",
        )
        self.assertEqual(
            tweets[1],
            "This is an absurdly long line that "
            "will become the basis for what"
            " should be preserved as line 4 but who knows what Twitter will "
            "do. This needs even more filler since Twitter decided to double"
            " its tweet character limit. I have no idea what else to put in "
            "here because everything",
        )
        self.assertEqual(
            tweets[2],
            "is awful. Enjoy Arby's.\r\nLine 5",
        )