Skip to content
Merged

Pcc04 #850

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
9 changes: 4 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
**pyc
**swp
**log
**.pyc
**.swp
**__pycache__
**.venv
**venv
**drafts
**.cache
**sqlite
**sqlite3
**.idea
**.vscode
**.DS_Store
Expand Down
3 changes: 3 additions & 0 deletions 04/rhys/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
data/*
!data/README.md
config.py
6 changes: 6 additions & 0 deletions 04/rhys/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
oauthlib==3.2.2
requests==2.28.1
requests-oauthlib==1.3.1
six==1.10.0
tweepy==4.10.1
urllib3
59 changes: 59 additions & 0 deletions 04/rhys/test_usertweets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from collections import namedtuple
import csv
import unittest
from unittest.mock import patch


from tweets import TWEETS # mock data
from usertweets import UserTweets, NUM_TWEETS

HANDLE = 'pybites'
MAX_ID = '819831370113351680'

Tweet = namedtuple('Tweet', ['id_str', 'created_at', 'text'])


def read_csv(fname):
with open(fname) as f:
has_header = csv.Sniffer().has_header(f.readline())
f.seek(0)
r = csv.reader(f)
if has_header:
next(r, None) # skip the header
return [Tweet(*tw) for tw in r] # list(r)


class TestUserTweets(unittest.TestCase):
def setUp(self):
super().setUp()
with patch('tweepy.API') as mock_timeline:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cause of issue + our oversight, fixed on master (template) branch: 7221ee2 - thanks.

mock_timeline.return_value = TWEETS
self.user = UserTweets(HANDLE, max_id=MAX_ID)

def tearDown(self):
self.user = None
super().tearDown()

def test_num_tweets(self):
self.assertEqual(len(self.user), NUM_TWEETS)

def test_first_tweet_returned_by_api(self):
tw_n = 0
self.assertEqual(self.user[tw_n].id_str, MAX_ID)
self.assertEqual(self.user[tw_n].created_at, TWEETS[tw_n].created_at)
self.assertEqual(self.user[tw_n].text, TWEETS[tw_n].text)

def test_read_back_from_cached_csv(self):
csv_tweets = read_csv(self.user.output_file)
self.assertEqual(len(csv_tweets), NUM_TWEETS)
tw_n = 0 # first
self.assertEqual(csv_tweets[tw_n].id_str, MAX_ID)
self.assertEqual(csv_tweets[tw_n].created_at,
str(TWEETS[tw_n].created_at))
self.assertEqual(csv_tweets[tw_n].text, TWEETS[tw_n].text)
tw_n = -1 # last
self.assertEqual(csv_tweets[tw_n].text, TWEETS[tw_n].text)


if __name__ == "__main__":
unittest.main()
109 changes: 109 additions & 0 deletions 04/rhys/tweets.py

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions 04/rhys/usertweets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from collections import namedtuple
import csv
import os

import tweepy

from config import CONSUMER_KEY, CONSUMER_SECRET
from config import ACCESS_TOKEN, ACCESS_SECRET

DEST_DIR = "data"
EXT = "csv"
NUM_TWEETS = 100

Tweet = namedtuple("Tweet", "id_str created_at text")


class UserTweets(object):
def __init__(self, handle, max_id=None):
"""Get handle and optional max_id.
Use tweepy.OAuthHandler, set_access_token and tweepy.API
to create api interface.
Use _get_tweets() helper to get a list of tweets.
Save the tweets as data/<handle>.csv"""
self.auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
self.auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)
self.api = tweepy.API(self.auth)
self.handle = handle
self.max_id = max_id
self._tweets = self._get_tweets()
self._save_tweets()

def _get_tweets(self):
"""Hint: use the user_timeline() method on the api you defined in init.
See tweepy API reference: http://docs.tweepy.org/en/v3.5.0/api.html
Use a list comprehension / generator to filter out fields
id_str created_at text (optionally use namedtuple)"""
tweets = self.api.user_timeline(
screen_name=self.handle, count=NUM_TWEETS, since_id=self.max_id
)
return tweets

def _save_tweets(self):
"""Use the csv module (csv.writer) to write out the tweets.
If you use a namedtuple get the column names with Tweet._fields.
Otherwise define them as: id_str created_at text
You can use writerow for the header, writerows for the rows"""
if not os.path.exists(DEST_DIR):
os.makedirs(DEST_DIR)

filename = f"{DEST_DIR}/{handle}.{EXT}"
with open(filename, mode="w") as f:
writer = csv.writer(f)
writer.writerow(["id_str", "created_at", "text"])
for tweet in self._tweets:
writer.writerow([tweet.id_str, tweet.created_at, tweet.text])

return filename

def __len__(self):
"""See http://pybit.es/python-data-model.html"""
return len(self._tweets)

def __getitem__(self, pos):
"""See http://pybit.es/python-data-model.html"""
return self._tweets[pos]


if __name__ == "__main__":

for handle in (
"pybites",
"bleachin",
"bbelderbos",
): # _juliansequeria no longer exists
print("--- {} ---".format(handle))
user = UserTweets(handle)
for tw in user[:5]:
print(tw)
print()
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Head over to [our platform](https://codechalleng.es/challenges/), click on one a
Browse (or checkout) [our community branch](https://github.com/pybites/challenges/tree/community) - all PRs get merged into that branch.

### How do I join your Slack Community?
Use [this link](https://join.slack.com/t/pybites/shared_invite/enQtNDAxODc0MjEyODM2LTNiZjljNTI2NGJiNWI0MTRkNjY4YzQ1ZWU4MmQzNWQyN2Q4ZTQzMTk0NzkyZTRmMThlNmQzYTk5Y2Y5ZDM4NDU) - and use our #codechallenges channel for coding questions.
Use [this link](https://pybit.es/community/) - and use our #codechallenges channel for coding questions.
Copy link
Collaborator

Choose a reason for hiding this comment

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

thank you


### Can I help you out with code reviewing?
We are still getting a pretty manageable number of PRs to be able to merge them all in ourselves, but we do want to give each PR a bit more of a code review. As we're pretty busy we want to turn this into a *community effort*. So if you want to help out merging PRs into our challenges branch, become a moderator (and mentor!), you can volunteer [on Slack](https://join.slack.com/t/pybites/shared_invite/enQtNDAxODc0MjEyODM2LTNiZjljNTI2NGJiNWI0MTRkNjY4YzQ1ZWU4MmQzNWQyN2Q4ZTQzMTk0NzkyZTRmMThlNmQzYTk5Y2Y5ZDM4NDU).
We are still getting a pretty manageable number of PRs to be able to merge them all in ourselves, but we do want to give each PR a bit more of a code review. As we're pretty busy we want to turn this into a *community effort*. So if you want to help out merging PRs into our challenges branch, become a moderator (and mentor!), you can volunteer [on Slack](https://pybit.es/community/).

### Is this a place where we gonna be cool... just like Fonzie?
Ah you mean something like a _Code of conduct_? We wrote this when we started 2 years ago and it still holds true: "Remember, we don't strive for the 'best' solution, it's not a competition! Learning more + better Python is the main objective. Respect the newbie. There is no right or wrong answer. Do as much or as little as you want. Just have fun!"
Expand Down