10
10
cast ,
11
11
)
12
12
13
+ from appdirs import user_cache_dir
14
+ from diskcache import Cache
15
+ from json import dumps
13
16
from pandas import CategoricalDtype , DataFrame , Series , to_datetime
14
17
from requests import Response , Session
15
18
from requests .auth import HTTPBasicAuth
33
36
34
37
# Make the linter happy about the unused variables
35
38
__all__ = ["Epidata" , "EpiDataCall" , "EpiDataContext" , "EpiRange" , "CovidcastEpidata" ]
36
-
39
+ CACHE_DIRECTORY = user_cache_dir ( appname = "epidatpy" , appauthor = "delphi" )
37
40
38
41
@retry (reraise = True , stop = stop_after_attempt (2 ))
39
42
def _request_with_retry (
@@ -73,8 +76,9 @@ def __init__(
73
76
params : Mapping [str , Optional [EpiRangeParam ]],
74
77
meta : Optional [Sequence [EpidataFieldInfo ]] = None ,
75
78
only_supports_classic : bool = False ,
79
+ use_cache = None ,
76
80
) -> None :
77
- super ().__init__ (base_url , endpoint , params , meta , only_supports_classic )
81
+ super ().__init__ (base_url , endpoint , params , meta , only_supports_classic , use_cache )
78
82
self ._session = session
79
83
80
84
def with_base_url (self , base_url : str ) -> "EpiDataCall" :
@@ -100,13 +104,23 @@ def classic(
100
104
"""Request and parse epidata in CLASSIC message format."""
101
105
self ._verify_parameters ()
102
106
try :
107
+ if self .use_cache :
108
+ with Cache (CACHE_DIRECTORY ) as cache :
109
+ cache_key = str (self ._endpoint ) + str (self ._params )
110
+ if cache_key in cache :
111
+ return cache [cache_key ]
103
112
response = self ._call (fields )
104
113
r = cast (EpiDataResponse , response .json ())
105
114
if disable_type_parsing :
106
115
return r
107
116
epidata = r .get ("epidata" )
108
117
if epidata and isinstance (epidata , list ) and len (epidata ) > 0 and isinstance (epidata [0 ], dict ):
109
118
r ["epidata" ] = [self ._parse_row (row , disable_date_parsing = disable_date_parsing ) for row in epidata ]
119
+ if self .use_cache :
120
+ with Cache (CACHE_DIRECTORY ) as cache :
121
+ cache_key = str (self ._endpoint ) + str (self ._params )
122
+ # Set TTL to 7 days (TODO: configurable?)
123
+ cache .set (cache_key , r , expire = 7 * 24 * 60 * 60 )
110
124
return r
111
125
except Exception as e : # pylint: disable=broad-except
112
126
return {"result" : 0 , "message" : f"error: { e } " , "epidata" : []}
@@ -130,6 +144,13 @@ def df(
130
144
if self .only_supports_classic :
131
145
raise OnlySupportsClassicFormatException ()
132
146
self ._verify_parameters ()
147
+
148
+ if self .use_cache :
149
+ with Cache (CACHE_DIRECTORY ) as cache :
150
+ cache_key = str (self ._endpoint ) + str (self ._params )
151
+ if cache_key in cache :
152
+ return cache [cache_key ]
153
+
133
154
json = self .classic (fields , disable_type_parsing = True )
134
155
rows = json .get ("epidata" , [])
135
156
pred = fields_to_predicate (fields )
@@ -175,6 +196,13 @@ def df(
175
196
df [info .name ] = to_datetime (df [info .name ], format = "%Y%m%d" )
176
197
except ValueError :
177
198
pass
199
+
200
+ if self .use_cache :
201
+ with Cache (CACHE_DIRECTORY ) as cache :
202
+ cache_key = str (self ._endpoint ) + str (self ._params )
203
+ # Set TTL to 7 days (TODO: configurable?)
204
+ cache .set (cache_key , df , expire = 7 * 24 * 60 * 60 )
205
+
178
206
return df
179
207
180
208
@@ -203,8 +231,9 @@ def _create_call(
203
231
params : Mapping [str , Optional [EpiRangeParam ]],
204
232
meta : Optional [Sequence [EpidataFieldInfo ]] = None ,
205
233
only_supports_classic : bool = False ,
234
+ use_cache : bool = False ,
206
235
) -> EpiDataCall :
207
- return EpiDataCall (self ._base_url , self ._session , endpoint , params , meta , only_supports_classic )
236
+ return EpiDataCall (self ._base_url , self ._session , endpoint , params , meta , only_supports_classic , use_cache )
208
237
209
238
210
239
Epidata = EpiDataContext ()
0 commit comments