2020from . import __version__
2121
2222
23- def caching (disk_or_memory , cache_directory = None ):
24- """
25- Decorator that allows caching the outputs of the BaseClient get methods.
26- Cache can be either disk- or memory-based.
27- Disk-based cache is reloaded automatically between runs if the same
28- cache directory is specified.
29- Cache is kept per each unique uid.
30-
31- ex:
32- >> client.get_pokemon(1) -> output gets cached
33- >> client.get_pokemon(uid=1) -> output already cached
34- >> client.get_pokemon(2) -> output gets cached
35-
36- Parameters
37- ----------
38- disk_or_memory: str
39- Specify if the cache is disk- or memory-based. Accepts 'disk' or 'memory'.
40- cache_directory: str
41- Specify the directory for the disk-based cache.
42- Optional, will chose an appropriate and platform-specific directory if not specified.
43- Ignored if memory-based cache is selected.
44- """
45- if disk_or_memory not in ('disk' , 'memory' ):
46- raise ValueError ('Accepted values are "disk" or "memory"' )
47-
48- # Because of the way the BaseClient get methods are generated, they don't get a proper __name__.
49- # As such, it is hard to generate a specific cache directory name for each get method.
50- # Therefore, I decided to just generate a number for each folder, starting at zero.
51- # The same get methods get the same number every time because their order doesn't change.
52- # Also, variable is incremented inside a list because nonlocals are only python 3.0 and up.
53- get_methods_id = [0 ]
54-
55- def memoize (func ):
56- if disk_or_memory == 'disk' :
57- if cache_directory :
58- # Python 2 workaround
59- if sys .version_info [0 ] == 2 and not isinstance (cache_directory , str ):
60- raise TypeError ('expected str' )
61-
62- cache_dir = os .path .join (cache_directory , 'pokepy_cache' , str (get_methods_id [0 ]))
63- else :
64- cache_dir = os .path .join (
65- appdirs .user_cache_dir ('pokepy_cache' , False , opinion = False ),
66- str (get_methods_id [0 ]))
67- cache = FileCache ('pokepy' , flag = 'cs' , app_cache_dir = cache_dir )
68- get_methods_id [0 ] += 1
69- else : # 'memory'
70- cache = {}
71-
72- cache_info_ = namedtuple ('CacheInfo' , ['hits' , 'misses' , 'size' ])
73- hits = [0 ]
74- misses = [0 ]
75-
76- def cache_info ():
77- return cache_info_ (hits [0 ], misses [0 ], len (cache ))
78-
79- def cache_clear ():
80- cache .clear () # for disk-based cache, files are deleted but not the directories
81- if disk_or_memory == 'disk' :
82- cache .create () # recreate cache file handles
83- hits [0 ] = 0
84- misses [0 ] = 0
85-
86- def cache_location ():
87- return 'ram' if disk_or_memory == 'memory' else cache .cache_dir
88-
89- @functools .wraps (func )
90- def memoizer (* args , ** kwargs ):
91- # arguments to the get methods can be a value or uid=value
92- key = str (args [1 ]) if len (args ) > 1 else str (kwargs .get ("uid" ))
93-
94- if key not in cache :
95- misses [0 ] += 1
96- cache [key ] = func (* args , ** kwargs )
97- else :
98- hits [0 ] += 1
99- return cache [key ]
100-
101- memoizer .cache_info = cache_info
102- memoizer .cache_clear = cache_clear
103- memoizer .cache_location = cache_location
104- return memoizer
105- return memoize
106-
107-
10823class V2Client (BaseClient ):
10924 """Pokéapi client"""
11025
@@ -174,23 +89,45 @@ def __init__(self, cache=None, cache_location=None, *args, **kwargs):
17489 cache directory, for disk-based cache.
17590 Optional.
17691 """
177- if cache == 'in_memory' :
178- cache_function = caching ('memory' )
179- self .cache_type = cache
180- elif cache == 'in_disk' :
181- cache_function = caching ('disk' , cache_location )
182- self .cache_type = cache
183- elif cache is None : # empty wrapping function
92+ if cache is None : # empty wrapping function
18493 def no_cache (func ):
18594 @functools .wraps (func )
18695 def inner (* args , ** kwargs ):
18796 return func (* args , ** kwargs )
18897 return inner
18998 cache_function = no_cache
190- else : # wrong cache parameter
191- raise ValueError ('Accepted values for cache are "in_memory" or "in_disk"' )
99+ else :
100+ if cache in ['in_memory' , 'in_disk' ]:
101+ cache_function = self ._caching (cache .split ('in_' )[1 ], cache_location )
102+ self .cache_type = cache
103+
104+ def cache_info_total (self ):
105+ return self ._cache_info_ (self ._cache_hits_global ,
106+ self ._cache_misses_global ,
107+ self ._cache_len_global )
108+
109+ def cache_clear_total (self ):
110+ for get_method_name in self ._all_get_methods_names :
111+ getattr (self , get_method_name ).cache_clear ()
192112
193- self .cache = cache_function
113+ def cache_location_absolute (self ):
114+ return self ._cache_location_global
115+
116+ # global cache related methods
117+ self .cache_info = types .MethodType (cache_info_total , self )
118+ self .cache_clear = types .MethodType (cache_clear_total , self )
119+ self .cache_location = types .MethodType (cache_location_absolute , self )
120+
121+ self ._cache_hits_global = 0
122+ self ._cache_misses_global = 0
123+ self ._cache_len_global = 0
124+ self ._cache_location_global = ''
125+ self ._cache_info_ = namedtuple ('CacheInfo' , ['hits' , 'misses' , 'size' ])
126+ else : # wrong cache parameter
127+ raise ValueError ('Accepted values for cache are "in_memory" or "in_disk"' )
128+
129+ self ._cache = cache_function
130+ self ._all_get_methods_names = []
194131 super (V2Client , self ).__init__ (* args , ** kwargs )
195132
196133 def _assign_method (self , resource_class , method_type ):
@@ -199,6 +136,7 @@ def _assign_method(self, resource_class, method_type):
199136 - uid is now first parameter (after self). Therefore, no need to explicitly call 'uid='
200137 - Ignored the other http methods besides GET (as they are not needed for the pokeapi.co API)
201138 - Added cache wrapping function
139+ - Added a way to list all get methods
202140 """
203141 method_name = resource_class .get_method_name (
204142 resource_class , method_type )
@@ -209,7 +147,7 @@ def _assign_method(self, resource_class, method_type):
209147 )
210148
211149 # uid is now the first argument (after self)
212- @self .cache
150+ @self ._cache
213151 def get (self , uid = None , method_type = method_type ,
214152 method_name = method_name ,
215153 valid_status_codes = valid_status_codes ,
@@ -225,3 +163,110 @@ def get(self, uid=None, method_type=method_type,
225163 self , method_name ,
226164 types .MethodType (get , self )
227165 )
166+
167+ # for easier listing of get methods
168+ self ._all_get_methods_names .append (method_name )
169+
170+ def _caching (self , disk_or_memory , cache_directory = None ):
171+ """
172+ Decorator that allows caching the outputs of the BaseClient get methods.
173+ Cache can be either disk- or memory-based.
174+ Disk-based cache is reloaded automatically between runs if the same
175+ cache directory is specified.
176+ Cache is kept per each unique uid.
177+
178+ ex:
179+ >> client.get_pokemon(1) -> output gets cached
180+ >> client.get_pokemon(uid=1) -> output already cached
181+ >> client.get_pokemon(2) -> output gets cached
182+
183+ Parameters
184+ ----------
185+ disk_or_memory: str
186+ Specify if the cache is disk- or memory-based. Accepts 'disk' or 'memory'.
187+ cache_directory: str
188+ Specify the directory for the disk-based cache.
189+ Optional, will chose an appropriate and platform-specific directory if not specified.
190+ Ignored if memory-based cache is selected.
191+ """
192+ if disk_or_memory not in ('disk' , 'memory' ):
193+ raise ValueError ('Accepted values are "disk" or "memory"' )
194+
195+ # Because of how BaseClient get methods are generated, they don't get a proper __name__.
196+ # As such, it is hard to generate a specific cache directory name for each get method.
197+ # Therefore, I decided to just generate a number for each folder, starting at zero.
198+ # The same get methods get the same number every time because their order doesn't change.
199+ # Also, variable is incremented inside a list because nonlocals are only python 3.0 and up.
200+ get_methods_id = [0 ]
201+
202+ def memoize (func ):
203+ _global_cache_dir = ''
204+
205+ if disk_or_memory == 'disk' :
206+ if cache_directory :
207+ # Python 2 workaround
208+ if sys .version_info [0 ] == 2 and not isinstance (cache_directory , str ):
209+ raise TypeError ('expected str' )
210+
211+ _global_cache_dir = os .path .join (cache_directory , 'pokepy_cache' )
212+ cache_dir = os .path .join (_global_cache_dir , str (get_methods_id [0 ]))
213+ else :
214+ _global_cache_dir = appdirs .user_cache_dir ('pokepy_cache' , False ,
215+ opinion = False )
216+ cache_dir = os .path .join (_global_cache_dir , str (get_methods_id [0 ]))
217+
218+ cache = FileCache ('pokepy' , flag = 'cs' , app_cache_dir = cache_dir )
219+ get_methods_id [0 ] += 1
220+ else : # 'memory'
221+ cache = {}
222+ _global_cache_dir = 'ram'
223+
224+ # global cache directory
225+ # should only be set when setting the first get method
226+ if not self ._cache_location_global :
227+ self ._cache_location_global = _global_cache_dir
228+
229+ hits = [0 ]
230+ misses = [0 ]
231+
232+ def cache_info ():
233+ return self ._cache_info_ (hits [0 ], misses [0 ], len (cache ))
234+
235+ def cache_clear ():
236+ # global cache info
237+ self ._cache_hits_global -= hits [0 ]
238+ self ._cache_misses_global -= misses [0 ]
239+ self ._cache_len_global -= len (cache )
240+ # local cache info
241+ hits [0 ] = 0
242+ misses [0 ] = 0
243+
244+ cache .clear () # for disk-based cache, files are deleted but not the directories
245+ if disk_or_memory == 'disk' :
246+ cache .create () # recreate cache file handles
247+
248+ def cache_location ():
249+ return 'ram' if disk_or_memory == 'memory' else cache .cache_dir
250+
251+ @functools .wraps (func )
252+ def memoizer (* args , ** kwargs ):
253+ # arguments to the get methods can be a value or uid=value
254+ key = str (args [1 ]) if len (args ) > 1 else str (kwargs .get ("uid" ))
255+
256+ if key not in cache :
257+ # local and global cache info
258+ misses [0 ] += 1
259+ self ._cache_misses_global += 1
260+ cache [key ] = func (* args , ** kwargs )
261+ self ._cache_len_global += 1
262+ else :
263+ self ._cache_hits_global += 1 # global cache info
264+ hits [0 ] += 1 # local cache info
265+ return cache [key ]
266+
267+ memoizer .cache_info = cache_info
268+ memoizer .cache_clear = cache_clear
269+ memoizer .cache_location = cache_location
270+ return memoizer
271+
272+ return memoize
0 commit comments