Skip to content

Commit 12cc424

Browse files
bbelderbospybites
authored andcommitted
Pcc25 (#51)
* init commit * rename * api / data retrieving / storage done * first version
1 parent 5eb9db3 commit 12cc424

File tree

9 files changed

+334
-0
lines changed

9 files changed

+334
-0
lines changed

25/bbelderbos/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
**sqlite
2+
**pkl
3+
**log
4+
**swp
5+
**pyc
6+
**.shelve*
7+
web

25/bbelderbos/main.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from collections import defaultdict
2+
from datetime import datetime
3+
import shelve
4+
5+
from themoviedb import Movies, Tv, CACHE
6+
from notifications import generate_mail_msg, mail_msg
7+
8+
SENT_CACHE = 'sent.shelve'
9+
10+
language = 'en'
11+
num_pages = 3
12+
13+
14+
def update_store():
15+
mo = Movies()
16+
mo.get_now_playing()
17+
mo.get_upcoming()
18+
tv = Tv()
19+
tv.get_on_the_air()
20+
tv.get_popular()
21+
22+
23+
def load_store():
24+
items = defaultdict(lambda: defaultdict(list))
25+
26+
with shelve.open(CACHE) as sh, shelve.open(SENT_CACHE) as ca:
27+
for key in sh:
28+
29+
if key in ca:
30+
continue
31+
ca[key] = datetime.utcnow()
32+
33+
entry = sh[key]
34+
items[entry.kind][entry.listing].append(
35+
entry)
36+
37+
return items
38+
39+
40+
if __name__ == '__main__':
41+
update_store()
42+
items = load_store()
43+
content = generate_mail_msg(items)
44+
mail_msg(content)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .mail import generate_mail_msg, mail_msg

25/bbelderbos/notifications/mail.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from datetime import datetime
2+
from email.mime.multipart import MIMEMultipart
3+
from email.mime.text import MIMEText
4+
import os
5+
import smtplib
6+
import sys
7+
8+
from themoviedb import get_genres_cache
9+
10+
BASE_IMG_URL = 'http://image.tmdb.org/t/p/w92{}'
11+
BASE_MOVIE_URL = 'https://www.themoviedb.org/{}/{}'
12+
SUBJECT = 'Movies / Series Digest'
13+
GENRES = get_genres_cache()
14+
15+
FROM_MAIL = os.environ.get('FROM_MAIL')
16+
TO_MAIL = os.environ.get('TO_MAIL')
17+
18+
if not FROM_MAIL or not TO_MAIL:
19+
print('set FROM_MAIL and TO_MAIL in env')
20+
sys.exit(1)
21+
22+
# TODO: retrieve director and actors
23+
TEMPLATE = '''<h4><a href="{link}">{title}</a></h4>
24+
<p>Overview: {overview}
25+
<img src="{img}" style="float: right;"></p>
26+
<p>Genres: {genres} / (first) release: {release}</p>
27+
<hr>
28+
'''
29+
30+
31+
def generate_mail_msg(items):
32+
output = []
33+
34+
for kind in items:
35+
output.append('<h2>{}</h2>'.format(kind))
36+
37+
for listing, entries in items[kind].items():
38+
output.append('<h3>{}</h3>'.format(listing))
39+
40+
if not entries:
41+
output.append('No new items')
42+
continue
43+
44+
for entry in sorted(entries,
45+
key=lambda x: datetime.strptime(
46+
x.release_date, '%Y-%m-%d'),
47+
reverse=True):
48+
49+
img = BASE_IMG_URL.format(entry.poster)
50+
kind_for_url = kind.replace('movies', 'movie')
51+
url = BASE_MOVIE_URL.format(kind_for_url, entry.id)
52+
genres = ', '.join([GENRES.get(gen) for gen in entry.genres
53+
if GENRES.get(gen)])
54+
55+
output.append(TEMPLATE.format(link=url,
56+
title=entry.title,
57+
overview=entry.overview,
58+
img=img,
59+
genres=genres,
60+
release=entry.release_date))
61+
62+
return '\n'.join(output)
63+
64+
65+
def mail_msg(content, recipients=TO_MAIL, subject=SUBJECT):
66+
if isinstance(recipients, list):
67+
recipients = ', '.join(recipients)
68+
69+
sender = FROM_MAIL
70+
71+
msg = MIMEMultipart('alternative')
72+
msg['Subject'] = subject
73+
msg['From'] = sender
74+
msg['To'] = recipients
75+
76+
part = MIMEText(content, 'html')
77+
msg.attach(part)
78+
79+
s = smtplib.SMTP('localhost')
80+
s.sendmail(sender, recipients, msg.as_string())
81+
s.quit()

25/bbelderbos/themoviedb/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .tmdb_api import Movies, Tv
2+
from .genres import get_genres_cache
3+
from .decorators import CACHE
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from collections import namedtuple
2+
from functools import wraps
3+
import re
4+
import shelve
5+
6+
CACHE = 'items.shelve'
7+
DEFAULT_OVERWRITE = True
8+
9+
Item = namedtuple('Item', 'id kind listing title genres overview release_date poster') # noqa E501
10+
11+
12+
def _get_title(item):
13+
# movies API uses 'title', series API uses 'name'
14+
title = item.get('title')
15+
16+
if not title:
17+
title = item.get('name')
18+
19+
return title
20+
21+
22+
def _get_release_date(item):
23+
# movies API uses 'release_date', series API uses 'first_air_date'
24+
release_date = item.get('release_date')
25+
26+
if not release_date:
27+
release_date = item.get('first_air_date')
28+
29+
return release_date
30+
31+
32+
def _store(kind, listing, resp, overwrite=DEFAULT_OVERWRITE):
33+
with shelve.open(CACHE) as sh:
34+
for item in resp:
35+
key = str(item['id'])
36+
37+
if overwrite and key in sh:
38+
del sh[key]
39+
40+
title = _get_title(item)
41+
if not title:
42+
continue
43+
44+
release_date = _get_release_date(item)
45+
if not release_date:
46+
continue
47+
48+
item = Item(id=item['id'],
49+
kind=kind,
50+
listing=listing,
51+
title=title,
52+
genres=item['genre_ids'],
53+
overview=item['overview'],
54+
release_date=release_date,
55+
poster=item['poster_path'])
56+
57+
sh[key] = item
58+
59+
60+
def store_results(f):
61+
@wraps(f)
62+
def wrapped(*args, **kwargs):
63+
class_name = str(args[0]).lower()
64+
kind = re.sub(r'.*\.(\S+)\sobject.*', r'\1', class_name)
65+
print(kind)
66+
func_name = str(args[1]).lower()
67+
listing = re.sub(r'.*bound.*?\.(\S+) of.*', r'\1', func_name)
68+
print(listing)
69+
resp = f(*args, **kwargs)
70+
_store(kind, listing, resp)
71+
print(len(resp))
72+
return resp
73+
return wrapped

25/bbelderbos/themoviedb/genres.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import os
2+
import pickle
3+
4+
from .tmdb_init import tmdb
5+
6+
CACHE = 'genres.pkl'
7+
8+
9+
def cache_genres():
10+
gen = tmdb.Genres()
11+
genres = gen.list()
12+
13+
genres = {g['id']: g['name'] for g in genres['genres']}
14+
15+
with open(CACHE, 'wb') as f:
16+
pickle.dump(genres, f)
17+
18+
19+
def get_genres_cache():
20+
if not os.path.isfile(CACHE):
21+
print('Cache file not found, generating one')
22+
cache_genres()
23+
24+
with open(CACHE, 'rb') as f:
25+
return pickle.load(f)
26+
27+
28+
if __name__ == '__main__':
29+
if os.path.isfile(CACHE):
30+
os.remove(CACHE)
31+
32+
genres = get_genres_cache()
33+
34+
from pprint import pprint as pp
35+
pp(genres)

25/bbelderbos/themoviedb/tmdb_api.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import time
2+
3+
from .tmdb_init import tmdb
4+
from .decorators import store_results
5+
6+
DEFAULT_LANG = 'en'
7+
DEFAULT_NUM_PAGES = 2
8+
DEFAULT_MIN_VOTE_COUNT = 1
9+
10+
11+
class Tmdb:
12+
13+
def __init__(self, language=None, num_pages=None, min_vote_count=None):
14+
self.language = language or DEFAULT_LANG
15+
self.num_pages = num_pages or DEFAULT_NUM_PAGES
16+
self.min_vote_count = min_vote_count or DEFAULT_MIN_VOTE_COUNT
17+
18+
@store_results
19+
def get_items(self, obj_method):
20+
results = []
21+
22+
for i in range(self.num_pages):
23+
page = i + 1
24+
25+
num_tries = 0
26+
resp = None
27+
28+
while num_tries < 3:
29+
try:
30+
resp = obj_method(language=self.language,
31+
page=page)
32+
break
33+
except:
34+
num_tries += 1
35+
continue
36+
37+
if not resp or 'results' not in resp:
38+
continue
39+
40+
results += [r for r in resp['results']
41+
if r['vote_count'] > self.min_vote_count]
42+
43+
time.sleep(1)
44+
45+
return results
46+
47+
48+
class Movies(Tmdb):
49+
50+
def __init__(self, language=None, num_pages=None):
51+
super().__init__(language, num_pages)
52+
self.movies = tmdb.Movies()
53+
54+
def get_now_playing(self):
55+
return self.get_items(self.movies.now_playing)
56+
57+
def get_upcoming(self):
58+
return self.get_items(self.movies.upcoming)
59+
60+
61+
class Tv(Tmdb):
62+
63+
def __init__(self, language=None, num_pages=None):
64+
super().__init__(language, num_pages)
65+
self.tv = tmdb.TV()
66+
67+
def get_popular(self):
68+
return self.get_items(self.tv.popular)
69+
70+
def get_on_the_air(self):
71+
return self.get_items(self.tv.on_the_air)
72+
73+
74+
if __name__ == '__main__':
75+
mo = Movies('en', 3)
76+
resp = mo.get_now_playing()
77+
print(len(resp))

25/bbelderbos/themoviedb/tmdb_init.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
import sys
3+
4+
import tmdbsimple as tmdb
5+
6+
key = os.environ.get('THE_MOVIE_DB_API_KEY')
7+
8+
if not key:
9+
print('Set themoviedb API_KEY in your env')
10+
print('export THE_MOVIE_DB_API_KEY=xyz')
11+
sys.exit(1)
12+
13+
tmdb.API_KEY = key

0 commit comments

Comments
 (0)