Skip to content
This repository was archived by the owner on Mar 31, 2020. It is now read-only.

Commit 9dd9e18

Browse files
author
Noah Corona
authored
Merge pull request #11 from Zer0897/cache
Async confuses me, so lets use multiprocessing
2 parents 1b7c92a + 66e7131 commit 9dd9e18

File tree

5 files changed

+116
-162
lines changed

5 files changed

+116
-162
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pillow = "*"
1313
pygame = "*"
1414
aiohttp = "*"
1515
configparser = "*"
16+
requests = "*"
1617

1718
[requires]
1819
python_version = "3.7"

Pipfile.lock

Lines changed: 28 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cache.py

Lines changed: 68 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,70 @@
1-
import aiohttp
2-
from random import randint, choice, sample
3-
from PIL import Image, ImageTk
4-
import io
5-
import configparser
6-
7-
from . import SETTINGS, IMAGES
8-
9-
10-
class Cache:
11-
'''Class used for caching images and data about the cats'''
12-
13-
def __init__(self, root):
14-
# setting root
15-
self.root = root
16-
self.screen_x = self.root.winfo_screenwidth()
17-
self.screen_y = self.root.winfo_screenheight()
18-
19-
# setting class variables for use later
20-
self.cats = list()
21-
self.session = None
22-
23-
# get settings
24-
cp = configparser.ConfigParser()
25-
cp.read(str(SETTINGS))
26-
27-
# for now, let's just look up the DEV settings
28-
# can change this later
29-
# configparser will use values from DEFAULT section if none provided elsewhere
30-
if 'DEV' in cp.sections():
31-
self.config = cp['DEV']
32-
else:
33-
self.config = cp['DEFAULT']
34-
35-
# getting the directory folder for use later when opening files
361

37-
async def refill(self):
38-
'''Gets a cache of cat data and adds it to the self.cats list'''
2+
import requests
3+
import time
4+
# import configparser
395

40-
# if we haven't created a session yet, do so
41-
if not self.session:
42-
self.session = aiohttp.ClientSession()
43-
44-
cachesize = int(self.config['cachesize'])
45-
46-
# Run 10 times to get 10 cats
47-
for i in range(cachesize):
48-
# initialize a dict of cat data
49-
cat_data = dict()
50-
51-
# randomly make jumpscares happen, but not on the first image
52-
if randint(1, 10) == 5 and i:
53-
# get a random number for an image
54-
image_number = randint(1, 10)
55-
# open and resize the image using Pillow
56-
imagepath = IMAGES / f"{image_number}.jpg"
57-
im = Image.open(imagepath)
58-
im = im.resize((self.screen_x, self.screen_y - 150), Image.NEAREST)
59-
# make the image a tkinter image
60-
image = ImageTk.PhotoImage(im)
61-
# update the cat data dict
62-
cat_data.update({"image": image})
63-
cat_data.update({"jumpscare": True})
64-
else:
65-
# set jumpscare to False because it isnt a jumpscare image
66-
cat_data.update({"jumpscare": False})
67-
68-
# get a url from The Cat API
69-
async with self.session.get('https://api.thecatapi.com/v1/images/search') as res:
70-
data = await res.json()
71-
url = data[0]['url']
72-
# get image data from that url
73-
async with self.session.get(url) as res:
74-
image_bytes = await res.read()
75-
# open and the image in pillow
76-
im = Image.open(io.BytesIO(image_bytes))
77-
im = im.resize((400, 280), Image.NEAREST)
78-
# make the image a tkinter image
79-
image = ImageTk.PhotoImage(im)
80-
# update the cat data dict
81-
cat_data.update({"image": image})
82-
83-
# get a random name
84-
async with self.session.get(
85-
"https://www.pawclub.com.au/assets/js/namesTemp.json") as res:
86-
data = await res.json()
87-
# get a random letter for the name
88-
# Note: website doesn't have any b names which is why it is left out here
89-
letter = choice(list('acdefghijklmnopqrstuvwxyz'))
90-
# randomly choose a name from the json with that letter
91-
cat = choice(data[letter])
92-
# update the cat data dict
93-
cat_data.update({"name": cat["name"]})
94-
cat_data.update({"gender": cat["gender"]})
95-
96-
# get 5 random hobbies
97-
async with self.session.get(
98-
"https://gist.githubusercontent.com/mbejda/" +
99-
"453fdb77ef8d4d3b3a67/raw/e8334f09109dc212892406e25fdee03efdc23f56/" +
100-
"hobbies.txt"
101-
) as res:
102-
text = await res.text()
103-
# split the raw text of hobbies into a list
104-
all_hobbies = text.split("\n")
105-
# get 5 of those hobbies
106-
hobby_list = sample(all_hobbies, 5)
107-
# join those 5 hobbies into a bulleted list (string)
108-
list_of_hobbies = "\n •".join(hobby_list)
109-
hobbies = f"Hobbies:\n{list_of_hobbies}"
110-
# update the cat_data dict
111-
cat_data.update({"hobbies": hobbies})
112-
113-
# get a random age between 1 and 15 (avg lifespan of a cat)
114-
age = str(randint(1, 15))
115-
# update the cat data dict
116-
cat_data.update({"age": age})
117-
118-
# get a random number of miles away between 1 and 5
119-
miles = randint(1, 5)
120-
location = f"{miles} miles away"
121-
# update the cat data dict
122-
cat_data.update({"location": location})
123-
self.cats.append(cat_data)
6+
from random import randint, choice, sample
7+
from multiprocessing import Process, Queue
8+
9+
# from . import SETTINGS, IMAGES
10+
11+
12+
class ImageCache:
13+
'''Class used for caching images'''
14+
image_api = 'https://api.thecatapi.com/v1/images/search'
15+
profile_api = "https://www.pawclub.com.au/assets/js/namesTemp.json"
16+
hobbies_api = (
17+
"https://gist.githubusercontent.com/mbejda/453fdb77ef8d4d3b3a67/raw/"
18+
"e8334f09109dc212892406e25fdee03efdc23f56/hobbies.txt"
19+
)
20+
ratelimit = 0.1
21+
22+
def __init__(self, size):
23+
self.queue = Queue(size)
24+
self.worker = None
25+
26+
def __del__(self):
27+
self.stop()
28+
29+
def get_image(self):
30+
res = requests.get(self.image_api, stream=True)
31+
data = res.json()
32+
url = data[0]['url']
33+
res = requests.get(url)
34+
return res.content
35+
36+
def get_hobbies(self):
37+
res = requests.get(self.hobbies_api)
38+
all_hobbies = res.text.split("\n")
39+
return sample(all_hobbies, 5)
40+
41+
def get_profile(self):
42+
res = requests.get(self.profile_api)
43+
data = res.json()
44+
letter = choice('acdefghijklmnopqrstuvwxyz')
45+
data = choice(data[letter])
46+
return {
47+
'name': data['name'],
48+
'gender': data['gender'],
49+
'age': randint(1, 42),
50+
'location': f'{randint(1, 9999)} miles away',
51+
'image': self.get_image(),
52+
'hobbies': self.get_hobbies()
53+
}
54+
55+
def next(self):
56+
return self.queue.get()
57+
58+
def mainloop(self, queue):
59+
while True:
60+
queue.put(self.get_profile())
61+
time.sleep(self.ratelimit)
62+
63+
def start(self):
64+
if self.worker is not None and self.worker.is_alive():
65+
self.stop()
66+
self.worker = Process(target=self.mainloop, args=(self.queue,))
67+
self.worker.start()
68+
69+
def stop(self):
70+
self.worker.terminate()

src/front.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
import asyncio
1+
import io
2+
from PIL import Image, ImageTk
23

34
from . import widget
45
from .animate import Direction
56
from .view import Window, View
6-
from .cache import Cache
7+
from .cache import ImageCache
8+
9+
10+
def process_image(image: bytes, width: int, height: int):
11+
im = Image.open(io.BytesIO(image))
12+
im = im.resize((width, height), Image.NEAREST)
13+
return ImageTk.PhotoImage(im)
714

815

916
class Front(widget.PrimaryFrame):
1017

11-
cachesize = 10
18+
cachesize = 20
1219

1320
# Quick fix to keep a reference count on the
1421
# last image, making sure the garbage collector
1522
# doesn't delete it before the animation ends
1623
_last = None
1724

1825
def __next(self):
19-
data: dict = self.cache.pop()
20-
data.pop('jumpscare') # not using it for now
21-
if 'name' in data: # TODO Fix
22-
name = data.pop('name')
23-
else:
24-
name = 'None'
25-
image = data.pop('image')
26+
data: dict = self.cache.next()
27+
image = process_image(data.pop('image'), 400, 280)
28+
name = data.pop('name')
2629
self.__load(name, image, data)
2730

2831
def __load(self, name, image, data):
@@ -61,9 +64,7 @@ def init(self):
6164
self.btn_dislike.pack(side='left')
6265
self.btn_like.pack(side='right')
6366

64-
self._loop = asyncio.get_event_loop()
65-
self.cache = Cache(self.window)
66-
self.cache
67+
self.cache = ImageCache(self.cachesize)
6768

6869
def cmd_dislike(self):
6970
self.__change_image('right')
@@ -79,13 +80,12 @@ def cmd_bio(self):
7980

8081
@property
8182
def cache(self):
82-
if len(self._cache.cats) < self.cachesize:
83-
self._loop.run_until_complete(self._cache.refill())
84-
return self._cache.cats
83+
return self._cache
8584

8685
@cache.setter
87-
def cache(self, data: list):
88-
self._cache = data
86+
def cache(self, imagecache: ImageCache):
87+
self._cache = imagecache
88+
self._cache.start()
8989
# Prime the pump
9090
self.__next()
9191
self.window.set_view(self.image)

src/view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import tkinter as tk
22
from typing import TypeVar
33
from enum import Enum
4+
from PIL import ImageTk
45

56
from . import widget
67
from .animate import Coord, Animater, Direction
7-
from .cache import ImageTk
88

99

1010
class ViewType(Enum):

0 commit comments

Comments
 (0)