1 from sqlalchemy
import Table
, Column
, Integer
, ForeignKey
, Text
, Date
, PickleType
, Float
2 from sqlalchemy
.ext
.declarative
import declarative_base
3 from sqlalchemy
import create_engine
4 from sqlalchemy
.orm
import sessionmaker
13 logger
= logging
.getLogger(__name__
)
16 A simple caching scheme for pure functions, supporting pure functions as
19 Changes of code do NOT make the cache invalid - so you should delete the
20 cache database yourself if you change any pure functions.
23 ## hodnoty def kwargs to ovsem meni, jen kdyz se fce predava parametrem (funkci kterou taky cachujeme),
24 ## nikoliv kdyz je volana primo
25 ## pze kdyz se predava parametrem, tak ta vnejsi fce nevi jaky ma def param
27 # By default (without running init_cache ) a dict (=> cache not persistent across runs & processes)
30 Base
= declarative_base()
31 class CacheLine(Base
):
33 Maps key -> value, saving time of creation, which is used as a criterion for time expiration.
35 __tablename__
= 'cacheline'
36 id = Column(Integer
, primary_key
=True)
38 key
= Column(Text
, index
=True)
39 value
= Column(PickleType
)
42 return "(%s, %s) -> %s" % (self
.key
, self
.time
, self
.value
)
45 return "CacheLine(%s)" % (str(self
))
48 """ The cache uses the same interface as dict."""
49 def __init__(self
, db_session
, expire
):
50 self
.session
= db_session
53 def delete_expired(self
):
54 expired_before
= time
.time() - self
.expire
55 self
.session
.query(CacheLine
).filter(CacheLine
.time
< expired_before
).delete()
58 def __getitem__(self
, key
):
60 q
= self
.session
.query(CacheLine
).filter(CacheLine
.key
== key
)
62 # if expiration rate set
64 expired_before
= time
.time() - self
.expire
66 q
= q
.filter(CacheLine
.time
> expired_before
)
69 by_time
= q
.order_by(CacheLine
.time
).all()
73 return by_time
[-1].value
77 def __setitem__(self
, key
, value
):
78 l
= CacheLine(time
=time
.time(), key
=key
, value
=value
)
85 if not isinstance(cache_object
, DBCacheObject
):
86 logging
.warn("Cannot remove expired elemets from cache - not a DBCacheObject")
89 logging
.info("Deleting expired cache rows...")
90 cache_object
.delete_expired()
95 if isinstance(cache_object
, DBCacheObject
):
96 it
= cache_object
.session
.query(CacheLine
).all()
98 it
= cache_object
.iteritems()
109 class PureFunction(object):
110 """PureFunction is a class that has nice function repr like
111 <pure_function __main__.f> instead of the default repr
112 <function f at 0x11e5320>.
114 By using it, the user declares, that calls to the same function with
115 same arguments will always (in time, accross different processes, ..)
116 have the same results and can be thus cached.
118 def __init__(self
, f
):
120 assert isinstance(f
, types
.FunctionType
)
121 functools
.update_wrapper(self
, f
)
123 def getargspec(self
):
124 return inspect
.getargspec(self
.f
)
126 def get_default_kwargs(self
):
127 args
, varargs
, varkw
, defaults
= self
.getargspec()
129 return dict(zip(args
[-len(defaults
):], defaults
))
131 def __call__(self
, *args
, **kwargs
):
132 logger
.debug("calling %s"%repr
(self
))
133 return self
.f(*args
, **kwargs
)
136 return '<pure_function %s>'%(utils
.repr_origin(self
.f
))
137 #return '<pure_function %s def_kwargs=%s>'%(utils.repr_origin(self.f), repr( self.get_default_kwargs()))
139 # to be used as a deco
140 declare_pure_function
= PureFunction
146 def init_cache(filename
='CACHE.db', expires
=0, sqlalchemy_echo
=False):
148 Initialize cache, sets up the global cache_object.
150 filename -- specifies the sqlite dbfile to store the results to
151 expires -- specifies expiration in seconds. If you set this to 0,
152 cached data are valid forever
153 echo -- whether to output sqlalchemy logs
156 # By default, the cache object is a dict
158 logger
.warn('Dictionary cache object does not support time expiration of cached values!')
160 engine
= create_engine('sqlite:///%s'%filename
, echo
=sqlalchemy_echo
)
161 Base
.metadata
.create_all(engine
)
162 Session
= sessionmaker(bind
=engine
)
166 cache_object
= DBCacheObject(session
, expires
)
170 cache_object
.session
.close()
172 def make_key(f
, f_args
, f_kwargs
):
173 if isinstance(f
, PureFunction
):
174 spect
= f
.getargspec()
175 elif isinstance(f
, types
.FunctionType
):
176 spect
= inspect
.getargspec(f
)
178 raise TypeError("Unable to obtain arg specification for function : '%s'"%(repr(f
)))
180 args
, varargs
, varkw
, defaults
= spect
183 default_kwargs
= dict(zip(args
[-len(defaults
):], defaults
))
184 for (key
, val
) in f_kwargs
.iteritems():
185 assert key
in default_kwargs
187 f_kwargs_joined
= default_kwargs
188 f_kwargs_joined
.update(f_kwargs
)
190 #rep = "%s(args=%s, kwargs=%s)"%(utils.function_nice_repr(f), repr(f_args), repr(f_kwargs_joined))
192 rep
= "%s(%s)"%(repr(f
),
193 ', '.join(map(repr, f_args
)
194 + [ '%s=%s'%(key
, repr( val
)) for key
, val
in f_kwargs_joined
.iteritems() ]))
196 ## XXX "normal temporary" objects
198 logger
.warn("Object(s) specified in '%s' do not have a proper repr."%(rep))
206 def cache_result(fun
):
207 """Compute the key, look if the result of a computation is in the
208 cache. If so, return it, otw run the function, cache the result and
211 def g(*args
, **kwargs
):
213 key
= make_key(f
, args
, kwargs
)
215 cached
= cache_object
[key
]
216 logger
.info("Returning CACHED for: '%s'"%(key))
219 ret
= f(*args
, **kwargs
)
220 cache_object
[key
] = ret
221 logger
.info("CACHING for: '%s'"%(key))
225 # if we got PureFunction, the returned function should also be pure
226 # please see the PureFunction.__doc__
227 if isinstance(fun
, PureFunction
):
229 functools
.update_wrapper(g
, fun
.f
)
230 return PureFunction(g
)
232 return functools
.wraps(fun
)(g_factory(fun
))
234 if __name__
== "__main__":
235 logging
.basicConfig()
236 l
= logging
.getLogger(__name__
)
237 l
.setLevel(logging
.INFO
)
239 init_cache(filename
=':memory:', expires
=0.1)
242 @declare_pure_function
247 @declare_pure_function
252 @declare_pure_function
254 return ( reduce( (lambda x
, y
: x
*y
) , l
), time
.time() )
264 """Stateless (pure) class and a pure function as arguments"""
267 """ The Adder must be `stateless` in a sense that results
268 of __call__ will always produce the same results for the
269 same args. Moreover the Adder must have __repr__ which has
270 all the information to uniquely define the Adder instance -
271 once again, so that the statement about __call__ holds.
273 The user is responsible for the statelessness!
274 (as with @declare_pure_function)
276 def __init__(self
, offset
):
278 def __call__(self
, a
, b
=10):
279 return a
+ self
.offset
281 return "Adder(offset=%s)"%self
.offset
286 @declare_pure_function
293 @declare_pure_function
294 def multiplicator(x
, mult
=2):
297 my_map(multiplicator
, range(10))
299 from utils
import partial
, partial_right
301 my_map(partial_right(multiplicator
, 2), range(10))
302 my_map(partial(a
, 2), range(10))
303 my_map(partial_right(multiplicator
, 2), range(10))
304 my_map(partial(a
, 2), range(10))
307 """Test warning for nonpure functions as arguments"""