Python Virtual Cron

Problem

Suppose you do want perform a certain task once in a while, and suppose you don’t have cron nor nothing similar. Suppose for example your environment is a CGI script and that it keeps almost no memory.

Of course every-time you run the script you can check if you are in a specified time range, and in that case you do run your action. In my opinion there is another way to accomplish this task: you can run the action based on a probability. This grants that the action is run in average m times every n uses (where m/n is the probability), and quite easily you can define an action that is run every-time (with n==m).

This approach in my opinion is better suited for a script that is called quite often and with action that need to be performed relatively often too, so that defining fine grained time ranges in which to run the supplementary actions becomes tedious.

Think for example in some database cleaning and consolidating. It’s more “use-based” than time-based.

Singleton

I did not write this particular code, but I use it in my vcron class. It’s freely available on the web, so no problem putting it here. As far as I know the author is Michele Simionato.

class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.instance=None
    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.instance

Put it in a file named “singleton.py” in the Python path or in the same directory as vcron.py.

vcron

Put the following code in a file called vcron.py. The license is a BSD based one, so you can do with it what you want, provided you give credit. I think it would be correct to give credit to Michele, too. You may check if you must on ActiveState Python Cookbook website, where I found this code. I am giving credit.

#  Copyright (c) 2004, Enrico Franchi
#  All rights reserved.
#  
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#  1) Redistributions of source code must retain the above copyright
#  notice, this list of conditions and the following disclaimer.
#  2) Redistributions in binary form must reproduce the above copyright
#  notice, this list of conditions and the following disclaimer in the
#  documentation and/or other materials provided with the distribution.
#  3) Neither my name nor the names of its contributors may be used to
#  endorse or promote products derived from this software without specific
#  prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
#  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import singleton
from random import randint

class VirtualCron:
    """VirtualCron emulates cron.

    This is used in environments where processes are not persistent and do
    not want to have memory of what happened, still they need to perform some
    tasks once in a while.
    It implements the singleton pattern, just one instance is allowed.

    Always use named arguments. Order of parameters is not guaranteed to remain the same.
    """
    __metaclass__ = singleton.Singleton

    FUNCTION = 0
    PARAMS = 1
    CHANCE = 2
    ACTIVE = 3
    RUNONCE = 4

    def __init__(self):
        self._actions = {}
    def add_action(self, name, function, params={}, chance=1000, active=True, runonce=False):
        """Adds selected action to VirtualCron object.

name is the name you use to access a record.
function is the function that will be called
params is the dictionary **kwds that will be passed to the function.
So it will be called function(**kwds) will be called.
1/ chance is the chance the function will be executed.
active means that the function is active
If runonce is True, then after being executed the function will be deactivated.
        """
        self._actions[name]=[function, params, chance, active, runonce]
    def del_action(self, name):
        del self._actions[name]
    def set_params(self, name, chance=None, runonce=None):
        if chance is not None:
            self._actions[name][self.CHANCE] = chance
        if runonce is not None:
            self._actions[name][self.RUNONCE] = runonce
    def activate(self, name):
        self._actions[name][self.ACTIVE]=True
    def deactivate(self, name):
        self._actions[name][self.ACTIVE]=False
    def change_named_parameter(self, name, arg_name, newvalue):
        rule = self._actions[name]
        kwds = rule[self.PARAMS]
        kwds[arg_name] = newvalue
    def run(self):
        for name, rule in self._actions.items():
            function, kwds, chance, active, runonce = rule
            if active and randint(1, chance)==1:
                function(**kwds)
                if runonce==True:
                    self.deactivate(name)

# some tests

## def printFactory(arg):
##     def _():
##         print arg
##     return _

## def printFactory2(arg):
##     def _(string):
##         print arg, string
##     return _

## v = VirtualCron()
## v.add_action('one', printFactory("one"))
## v.add_action('two', printFactory2("two"), params={'string':'kowabunga'})
## v.add_action('freq', printFactory("frequentissima"), chance=100)
## v.add_action('unique', printFactory("=====UNICA!======"), runonce=True)

## for i in xrange(10000):
##     v.run()
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: