Example #1
0
class TestCache(unittest.TestCase):
    def setUp(self):
        self.client = Redis()
        self.client.flushdb()

        self.cache = Cache(self.client, "hash-key")

    def test_set_and_get(self):
        self.assertIsNone(self.cache.get("msg"))
        self.cache.set("msg", "hello world")
        self.assertEqual(self.cache.get("msg"), "hello world")

    def test_is_exists(self):
        self.assertFalse(self.cache.is_exists("msg"))
        self.cache.set("msg", "hello world")
        self.assertTrue(self.cache.is_exists("msg"))

    def test_size(self):
        self.assertEqual(self.cache.size(), 0)
        self.cache.set("msg", "hello world")
        self.assertEqual(self.cache.size(), 1)

    def test_delete(self):
        self.assertFalse(self.cache.delete("msg"))
        self.cache.set("msg", "hello world")
        self.assertTrue(self.cache.delete("msg"))
Example #2
0
def cache_test():
    """
        TEST CASE FOR CACHE SYSTEM.

        :return:
    """
    # Test the cache
    Keys = [i for i in range(random.randint(1, 10))]  # Total Entries
    place = [
        'Imalia',
        'Brazil',
        'Canada',
        'Zimbabawe',
        'Saturn?',
        'New Zealand',
        'São Paulo',
        'Dubai',
        'Mars?',
        'Singapore',
        'Los Angeles',
        'Santos',
        'São Tomé',
        'Luxemburg',
        'New Deli',
        'Abu Dhabi',
        'São Vicente',
    ]

    # Cache object
    cache = Cache()

    # Updating Cache with entries
    for i, key in enumerate(Keys):
        if key in cache:
            continue
        else:
            value = ''.join([random.choice(place)])
            print('\t', value)
            cache.update(key, value)

        print("{0}.Iteration, #{1} cached entries".format(i + 1, cache.size()))

    # Cache List
    print('\n\n\t', '*' * 10, ' Places List ', '*' * 10)
    for k, v in cache.view().items():
        print("{0} : {1}".format(k, v))

    print("Cache size: ", cache.size())

    print("Cache overview: ", cache.view())

    print("delete the oldest item from cache: ", cache.delete())

    print("Erasing the cache: ", cache.empty())

    # end of the test
    print('_' * 60)
Example #3
0
def delete(url):
    """Delete <url> from cache"""
    url = urllib.unquote(url)
    if url.endswith("/"):
        raise Error("illegal url - can't delete a directory")

    print "* del: removing file from cache..."
    cache = Cache()
    result = cache.delete(url)

    return result
Example #4
0
def delete(url):
    """Delete <url> from cache"""
    url = urllib.unquote(url)
    if url.endswith("/"):
        raise Error("illegal url - can't delete a directory")

    print "* del: removing file from cache..."
    cache = Cache()
    result = cache.delete(url)

    return result
Example #5
0
class Twitter:
    """
    Module for core twitter service.
    """
    def __init__(self, mc, config):
        self.timelines = defaultdict(list)
        self.relations = defaultdict(set)
        self.tweets_cache = config['tweets_cache']
        self.feed_num = config['feed_num']
        self.relation_cache = Cache(mc, prefix='REL', dup=False)
        self.user_tweet_cache = Cache(mc,
                                      prefix='TWT',
                                      limit=self.tweets_cache)
        self.tweet_cache = Cache(mc)

    def post_tweet(self, user_id, tweet):
        # create db entry
        kwargs = {
            'user_id': user_id,
            'content': tweet,
            'time_stamp': str(int(time.time()))
        }
        tweet_db = model.Tweet.create(**kwargs)
        # update user's top tweets
        self.user_tweet_cache.update(
            user_id, (tweet_db.id, tweet_db.user_id, tweet_db.time_stamp))
        # cache tweets
        self.tweet_cache.set(str(tweet_db.id), tweet)
        return tweet_db.id

    def follow(self, follower_id, followee_id):
        if follower_id == followee_id:
            return
        # create db entry
        kwargs = {'follower': follower_id, 'followee': followee_id}
        model.Relation.create(**kwargs)
        # update user relation cache
        self.relation_cache.update(follower_id, followee_id)

    def unfollow(self, follower_id, followee_id):
        if follower_id == followee_id:
            return
        try:
            # create db entry
            model.Relation.get(follower=follower_id,
                               followee=followee_id).delete()
        except DoesNotExist:
            log.info('Relation entry not found %s %s' %
                     (follower_id, followee_id))
        # update user relation cache
        self.relation_cache.remove(follower_id, followee_id)

    def get_feed(self, user_id):
        candidates = []
        for user in self.get_follow(user_id) + [user_id]:
            log.info('Search tweets for user %s' % user)
            # try get user top tweets from cache first
            res = self.user_tweet_cache.get(user)
            if res is None:
                # get tweets from database
                candidates += self.get_tweet(user,
                                             limit=self.tweets_cache,
                                             update_cache=True)
            else:
                # load tweets from cache
                log.info('Load tweets from cache for user %s' % user)
                for tweet in res:
                    candidates.append(
                        Tweet(tweet[0], tweet[1], tweet[2], self.tweet_cache))
        # sort tweets
        candidates.sort(key=lambda x: x.timestamp, reverse=True)
        return [str(c) for c in candidates[:self.feed_num]]

    def get_tweet(self, user_id, limit=None, update_cache=False):
        # read tweets from db
        tweets = sorted(model.Tweet.filter(user_id=user_id),
                        key=lambda x: x.time_stamp,
                        reverse=True)
        candidates = []
        cache_list = []
        for i in range(len(tweets)):
            if limit is not None and i > limit:
                break
            tweet = tweets[i]
            log.info('Find tweets %s in db for %s' % (tweet.id, tweet.user_id))
            candidates.append(
                Tweet(tweet.id, tweet.user_id, tweet.time_stamp,
                      self.tweet_cache))
            cache_list.append((tweet.id, tweet.user_id, tweet.time_stamp))
        if update_cache:
            # update user's top tweet cache
            self.user_tweet_cache.set(user_id, cache_list)
        return candidates

    def delete_tweet(self, user_id, tweet_id):
        try:
            # delete db entry
            tweet = model.Tweet.get(id=tweet_id)
        except DoesNotExist:
            log.warning('Tweet %s not found' % id)
        # update user top tweets cache
        self.user_tweet_cache.remove(
            user_id, (tweet.id, tweet.user_id, tweet.time_stamp))
        tweet.delete()

    def get_follow(self, user_id):
        # try get followee from cache
        res = self.relation_cache.get(user_id)
        if res is None:
            # get followee from db
            res = [
                str(x.followee)
                for x in model.Relation.filter(follower=user_id)
            ]
            self.relation_cache.set(user_id, res)
        else:
            log.info('Get follow from cache for user %s' % user_id)
        return res

    def add_user(self, name):
        # create db entry
        kwargs = {'name': name}
        return model.User.create(**kwargs).id

    def get_user(self, id):
        try:
            # get user from db
            return model.User.get(id=id).name
        except DoesNotExist:
            log.warning('User %s not found' % id)
            return None

    def delete_user(self, id):
        try:
            # delete db entry
            model.User.get(id=id).delete()
        except DoesNotExist:
            log.warning('User %s not found' % id)
        # delete relation
        for relation in model.Relation.filter(followee=id).allow_filtering():
            self.unfollow(relation.follower, relation.followee)
        for relation in model.Relation.filter(follower=id):
            relation.delete()
        # delete cache
        self.relation_cache.delete(id)
        self.user_tweet_cache.delete(id)
        for tweet in model.Tweet.filter(user_id=id):
            tweet.delete()
Example #6
0
def reset_seen():
    redis = Cache()
    n = redis.delete(*redis.keys('*-seen'))
    return jsonify({'status_code': 200, 'data': n})
Example #7
0
def reset_user_seen(mac_address):
    redis = Cache()
    n = redis.delete(*redis.keys('%s-seen' % (mac_address)) +
                     ["__deletekey__"])
    return jsonify({'status_code': 200, 'data': n})
Example #8
0
def delete_user(mac_address):
    redis = Cache()
    print('Deleting entry for %s' % (mac_address))
    success = redis.delete(mac_address) > 0
    return jsonify({'status_code': 200, 'data': success})
Example #9
0
class PollingServer:
    def __init__(self, host, port, cache_size, db_name, workers):
        self.host = host
        self.port = port
        self.cache = Cache(cache_size)
        self.persistent = Persistent(db_name)
        self.req_queue = Queue() # Unbounded queue
        self.resp_queue = Queue()
        self.pool = {}
        self.workers = workers #number of workers in thread pool
        self.polling = None
        self.pending_reqs = {} #dict to store pending requests on a key

    def init_helpers(self):
        for i in range(self.workers):
            self.pool[i] = Thread(target=io_handler, \
                                  args=(self.req_queue, \
                                        self.resp_queue,\
                                        self.persistent, ))
                                  
    def run_helpers(self):    
        for thread in self.pool.values():
            thread.start()

    def issue_request(self, req):
        if req.key not in self.pending_reqs:
            self.req_queue.put(req, block=False)
            self.pending_reqs[req.key] = [req]
        else:
            pend = self.pending_reqs[req.key]
            pend.append(req)
            self.pending_reqs[req.key] = pend

    def get_responses(self):
        responses = []
        for i in range(self.workers):
            response = self.resp_queue.get(block=False)
            if len(self.pending_reqs[response.key])==1:
                del self.pending_reqs[response.key]
            else:
                self.req_queue.put(self.pending_reqs[response.key][0], block=False)
                new_pending = self.pending_reqs[response.key]
                new_pending.pop(0)
                self.pending_reqs[response.key] = new_pending
            responses.append(response)
            if self.resp_queue.empty()==True:
                break
        return responses

    def __bring_to_cache(self, key, value, dirty):
        retkey, retval, succ = self.cache.insert(key, value, dirty)
        if retkey and retval:
            # Writeback dirty eviction
            wb_req = Request('WRITEBACK', retkey, retval)
            self.issue_request(wb_req)
        return succ

    def run_server(self):
        self.polling = PollingSocket(self.host, self.port)
        self.init_helpers()
        self.run_helpers()
        #num_req = 0
        #first = True
        #tot_time = 0

        while True:
            requests = self.polling.poll_connection()
            #start = time.time()
            #num_req += len(requests)
            #if num_req:
            #    if first:
            #        tot_time = 0
            #        first = False
            #    if tot_time >= 1:
            #        with open('ep.lg', 'a') as f:
            #            f.write(str(num_req)+" "+str(tot_time)+"\n")
            #        #print(str(num_req)+" "+str(tot_time)+"\n")
            #        num_req = 0
            #        tot_time = 0

            for request in requests:
                #Request is a tuple of sock, request_data
                data = request[1]
                #Process request here
                req = parse_req(data.decode('utf-8'))
                req.fd = request[0]

                if req.op == 'GET':
                    val = self.cache.get(req.key)
                    if val is None:
                        # Cache miss: non-blocking put to req_queue
                        # for helper_threads to consume 
                        self.issue_request(req)
                    else:
                        resp = val
                        add_response(self.polling.sock_data_map, request[0], resp)
                
                elif req.op == 'PUT':
                    retval = self.cache.put(req.key, req.value)    
                    if retval is None:
                        #Cache miss
                        self.issue_request(req)
                    else:
                        resp = "ACK"
                        add_response(self.polling.sock_data_map, request[0], resp)

                elif req.op in ['INSERT', 'DELETE']:
                    self.issue_request(req)

                else:
                    resp = "-1".encode('utf-8')
                    add_response(self.polling.sock_data_map, request[0], resp)

            # Consume from response queue (non-blocking)
            try:
                responses = self.get_responses()
                for resp in responses:
                    #Ignore for write-back requests
                    if resp.fd is None:
                        continue
                    
                    #resp.value is actual value for GET
                    #for PUT/INSERT resp.value is "ACK" message
                    #resp.value is "-1" for all errors
                    #Delete needs special check if nothing to 
                    #delete from both cache and persistent
                    if resp.op != 'DELETE':
                        add_response(self.polling.sock_data_map, resp.fd, resp.value)
                    
                    #Entry brought to cache line for GET/PUT miss and INSERT
                    if resp.op in ['GET', 'PUT', 'INSERT']:
                        if resp.value != "-1":
                            self.__bring_to_cache(resp.key, resp.value, False)
                    elif resp.op == 'DELETE':
                        result = self.cache.delete(req.key)
                        if resp.value == "-1" and result is False:
                            #Nothing to delete from cache/ persistent
                            add_response(self.polling.sock_data_map, resp.fd, "-1")
                        else:
                            add_response(self.polling.sock_data_map, resp.fd, "ACK")
                    else:
                        pass
                #self.cache.show()
            except:
                pass
Example #10
0
class Database(object):
    def __init__(self, multi_value=False, cache_port=None, router=None, domain=None):
        self._db = CommonDB(name=self.name, router=router, domain=domain)
        self._multi_value = multi_value
        if cache_port != None:
            self._cache = Cache(cache_port)
        else:
            self._cache = None

    @property
    def name(self):
        return self.__class__.__name__.lower()

    def get(self, key, first=False):
        val = None
        cache = False
        if self._cache:
            cache = True
            if not self._multi_value or first:
                val = self._cache.get(key)

        if not val:
            coll = self._db.collection(key)
            conn = self._db.connection(coll)
            val = self._db.get(conn, key)

            if self._multi_value:
                if first and type(val) == list:
                    val = val[0]
                else:
                    cache = False

            if cache:
                self._cache.put(key, val)
        return val

    def put(self, key, value):
        if self._cache:
            self._cache.delete(key)
        if not self._multi_value:
            val = {'$set':{VAL_NAME:value}}
        else:
            val = {'$addToSet':{VAL_NAME:value}}
        coll = self._db.collection(key)
        conn = self._db.connection(coll)
        self._db.put(conn, key, val, create=True)

    def delete(self, key, value=None, regex=False):
        if self._cache:
            self._cache.delete(key)

        coll = self._db.collection(key)
        conn = self._db.connection(coll)
        if not value:
            self._db.delete(conn, key)
        else:
            if not self._multi_value:
                log_err(self, 'failed to delete')
                raise Exception(log_get(self, 'failed to delete'))
            if not regex:
                self._db.put(conn, key, {'$pull':{VAL_NAME:value}})
            else:
                self._db.put(conn, key, {'$pull':{VAL_NAME:{'$regex':value}}})
Example #11
0
class DataManager(DataTable):
    """
    This class provides high level access to a database table.
    
    It loads and saves data from the table into an object or a DataSet,
    or a subclass thereof. The class of objects and data sets that
    are returned can be set using the set_object() and set_dataset()
    functions.
    
    You can request a single object (row), or you can specify a set of criteria,
    which are then translated into a WHERE clause.
    """
    def __init__(self, table_name, field_dictionary):
        DataTable.__init__(self, table_name, field_dictionary)
        self.table = DataTable(table_name, field_dictionary)
        self.reset_cache()

    def reset_cache(self):
        """
        Initialize this data manager's object cache.

        Any contents in the cache are discarded.
        """
        self.cache = Cache()
        self.all_cached = 0
        self.last_synched = ''

    def preload(self):
        """
        Handles a client request to preload all objects from the database,
        fully populating the local cache and preparing for subsequent
        client requests.

        Preloading can result in significant performance benefits,
        because data managers who have preloaded their cache can
        fill more client requests from cache, avoiding expensive
        database accesses.

        Not all requests for preloading are honored. To be preloaded,
        a datamanager's cache size must be set to CACHE_UNLIMITED.
        Also, only one call is honored. Subsequent calls are
        silently ignored. This makes it safe and efficient for
        clients to request preloading, without needing to know
        whether or not the data manager has already been preloaded.
        """
        if self.all_cached == 1:
            return

        # FIXME: Also try to preload caches that are not unlimited,
        # but are large enough to hold all existing objects.
        # Use a SQL COUNT(*) function to make this determination,
        # so we don't waste lots of time attempting to preload
        # a cache that cannot be preloaded.
        if self.cache.size <> CACHE_UNLIMITED:
            return
        #print 'Preloading ' + self.table.name
        self.get_all()
        #i18n_table = self.table.name + '_i18n'
        #if self.dms.has_key(i18n_table):
        #    i18n_dm = self.dms[i18n_table]
        #    i18n_dm.preload()

    def get_by_id(self, id):
        """
        Returns an individual object whose primary key field matches
        the specified id.

        If the object is in the cache, the cached object is returned.
        Objects which have more than one primary key field cannot be
        reliably retrieved using this function. In this event, only the
        first matching object will be returned.
        """

        object = self.cache.get_by_key(id)
        if object == None:
            data_field = self.table.id_field()
            sql = self.table.select + ' WHERE ' + data_field.field_name + '=' + data_field.attr_to_field(
                id)
            cursor = db.select(sql)
            row = cursor.fetchone()
            if row == None: return
            object = self.row_to_object(row)
        return object

    def get_by_keys(self, filters):
        """
        Returns all objects which match the supplied filters.
        """
        if self.cache.filled == 1:
            self.all_cached = 0
        if self.all_cached == 1:
            return self.get_cached_by_keys(filters)
        else:
            sql = self.filters_to_sql(filters)
            return self.get_sql(sql)

    def get_cached_by_keys(self, filters):
        """
        This private function fills keyed requests directly from the
        object cache.
        
        No checking is performed to determine whether the cache contains
        all objects which fit the request. Therefore, this function
        should only be called by data managers whose caches are preloaded.
        See the preload() function for more information on preloading.
        """
        sql = self.filters_to_sql(filters)
        function_text = self.filters_to_function(filters)
        print 'Function text: '
        print function_text
        code = compile(function_text, '<string>', 'exec')
        print 'Code: ' + str(code)
        print 'Code has %s arguments.' % code.co_argcount

        self.test_object_filters.im_func.func_code = code
        #print 'Method code: ' + str(self.test_object_filters.im_func.func_code)
        good_keys = filter(self.test_object_filters, self.cache.keys())
        print 'Good keys: ' + str(good_keys)
        dataset = self.new_dataset()
        for key in good_keys:
            dataset[key] = self.cache[key]
        return dataset

    def test_object_filters(self, key):
        return 1

    def filters_to_function(self, filters):
        """
        Converts a list of filters into a Python function which tests
        an object to see if it matches the filters.

        Precompiling filter tests speeds up key filtering enormously.
        
        The generated function accepts a single parameter, "key".
        It retrieves the object with that key in the object
        cache and tests for a match. If the object matches all
        the filters, the generated function returns 1.
        Otherwise, it returns 0.
        """
        code = WOStringIO()
        code.write('def test_cached_object(key):\n')
        code.write('    object = self.cache[key]\n')
        for filter in filters:
            attribute, operator, value = filter
            test_value = repr(value)
            code.write('    obj_value = object.%s\n' % (attribute))
            if operator.upper() == 'LIKE':
                code.write('    if %s > len(%s): return 0\n' %
                           (len(value), obj_value))
                code.write('    return (%s <> obj_value.upper()[:%s])\n' %
                           (test_value.upper(), len(value)))
            elif operator in ['<>', '<', '<=', '=', '>=', '>']:
                if operator == '=':
                    operator = '=='
                code.write('    return (object.%s %s %s)\n' %
                           (attribute, operator, repr(value)))
            else:
                raise UnknownOperator('Unrecognized operator: %s' % (operator))

        return code.get_value()

    def filters_to_sql(self, filters):
        """
        Converts a list of filters into the SQL statement which will
        retrieve matching records from the database.
        """
        wheres = []
        for filter in filters:
            attribute, operator, value = filter
            field = self.table.fields.find_attribute(attribute)
            if operator.upper() == 'LIKE':
                wheres.append('upper(' + field_name + ') LIKE ' +
                              field.attr_to_field(value.upper() + '%'))
            else:
                wheres.append(field.field_name + operator +
                              field.attr_to_field(value))
        where = ' WHERE ' + string.join(wheres, ' AND ')
        return self.table.select + where

    def get_all(self):
        """
        Returns a set of all objects managed by this data manager.

        If the data manager's cache proves sufficient to cache all objects,
        the cache will subsequently be considered preloaded, i.e.,
        subsequent calls to get_by_keys() will be served directly from the
        cache, bypassing expensive database accesses. See the preload()
        function for more information on preloading.
        """
        if self.cache.filled == 1:
            self.all_cached = 0
        if self.all_cached == 0:
            #print 'Loading all of ' + self.table.name + ' into cache.'
            set = self.get_sql(self.table.select)
            if self.cache.filled == 0:
                self.all_cached = 1
            return set
        return self.get_cached()

    def synch(self):
        """
        Synchronize objects in the object cache with the database.

        Objects which have been deleted in the database are removed
        from the object cache.
        
        Objects which are out of synch with their database record
        have their attribute set to match the data in the database.
        """
        #print 'Synchronizing ' + self.table.name + ' with database'
        last_synched = self.last_synched  # Remember this, because we're about to overwrite it.
        self.last_synched = now_string()

        # Delete any newly deleted objects.
        sql = 'SELECT identifier FROM deleted WHERE table_name=' + wsq(
            self.table.name) + ' AND deleted >= ' + wsq(last_synched)
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row == None: break

            # Load keys for the deleted object
            object = self.new_object()
            if len(self.table.key_list) == 1:
                field = self.table.fields[self.table.key_list[0]]
                value = field.field_to_attr(row[0])
                setattr(object, field.attribute, value)
            else:
                values = row[0].split()
                for key in self.table.key_list:
                    field = self.table.fields[key]
                    value = field.field_to_attr(values[field.index])
                    setattr(object, field.attribute, value)
            object.key = self.table.get_key(object)

            #print 'Deleting from ' + self.table.name + ' cache: ' + str(value)
            self.cache.delete(object)

            # FIXME: Delete the object from all data sets which contain it!

        # Update any newly updated objects.
        sql = self.table.select + ' WHERE updated >= ' + wsq(last_synched)
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row == None: break
            key = self.table.get_row_key(row)
            if self.cache.has_key(key):
                object = self.cache[key]
                self.table.load_row(object, row)
                #print 'Updating in ' + self.table.name + ' cache: ' + str(object.key)
            else:
                object = self.row_to_object(row)
                self.cache.add(object)
                #print 'Adding in ' + self.table.name + ' cache: ' + str(object.key)

                # FIXME: Add the object to all data sets whose filters it matches.

    def get_cached(self):
        """
        Returns a dataset containing all objects in the object cache.
        """
        #print 'Pulling ' + self.table.name + ' from cache.'
        dataset = self.new_dataset()
        for key in self.cache.keys():
            dataset[key] = self.cache[key]
        return dataset

    def get_sql(self, sql):
        """
        Accepts a SQL statement, instantiates the corresponding objects from the
        database, and stores those objects in the data cache if possible.
        """
        #print 'Cache miss, loading: ' + self.table.name
        dataset = self.new_dataset()
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row == None: break
            object = self.row_to_object(row)
            dataset[object.key] = object
            self.cache.add(object)
        return dataset

    def set_object_class(self, object_class):
        self.object_class = object_class

    def set_dataset_class(self, dataset_class):
        self.dataset_class = dataset_class

    def new_object(self):
        object = self.object_class(self.dms, self)
        for key in self.table.fields.keys():
            field = self.table.fields[key]
            setattr(object, field.attribute, field.get_default())
        object.changed = 0
        object.in_database = 0
        return object

    def new_dataset(self):
        return self.dataset_class(self)

    def row_to_object(self, row):
        object = self.new_object()
        self.table.load_row(object, row)
        return object

    def add(self, object):
        self.save(object)

    def save(self, object):
        object.key = self.table.get_key(
            object)  # New objects need their key calculated.
        if object.changed == 0 and object.in_database == 0:
            return
        if object.in_database == 0:
            field_list = []
            value_list = []
            for key in self.table.field_list:
                field = self.table.fields[key]
                if field.data_type == 'created':  # The database is responsible for setting the timestamp.
                    continue
                if field.data_type == 'sequence':  # When inserting, always increment the value.
                    new_id = db.next_id(self.name, field.field_name)
                    setattr(object, field.attribute, new_id)
                value = field.attr_to_field(getattr(object, field.attribute))
                field_list.append(field.field_name)
                value_list.append(value)
            sql = 'INSERT INTO %s (%s) VALUES (%s)' % (
                self.table.name, string.join(
                    field_list, ', '), string.join(value_list, ', '))
        else:
            update_list = []
            where_list = []
            for key in self.table.field_list:
                field = self.table.fields[key]
                if field.data_type == 'created':
                    continue
                if field.data_type == 'updated':
                    value = wsq(now_string())
                else:
                    value = field.attr_to_field(
                        getattr(object, field.attribute))
                update_list.append(field.field_name + '=' + value)
                if field.key_field == 1:
                    where_list.append(field.field_name + '=' + value)
            sql = 'UPDATE %s SET %s WHERE %s' % (
                self.table.name, string.join(
                    update_list, ', '), string.join(where_list, ' AND '))
#            print sql
        db.runsql(sql)
        db.commit()
        self.cache.add(object)
        object.in_database = 1
        object.changed = 0

    def delete(self, object):
        if object.in_database == 0:
            return
        self.cache.delete(object)
        wheres = []
        for key in self.table.key_list:
            data_field = self.table.fields[key]
            value = data_field.attr_to_field(
                getattr(object, data_field.attribute))
            wheres.append(data_field.field_name + '=' + value)
        where = ' WHERE ' + string.join(wheres, ' AND ')
        sql = 'DELETE FROM %s %s' % (self.table.name, where)
        db.runsql(sql)
        db.commit()
        sql = 'INSERT INTO deleted (table_name, identifier) VALUES (%s, %s)' % (
            wsq(self.table.name), wsq(str(object.key)))
        db.runsql(sql)
        db.commit()

    def delete_by_keys(self, filters):
        dataset = self.get_by_keys(filters)
        for key in dataset.keys():
            object = dataset[key]
            self.delete(object)

    def clear(self, dataset):
        for key in dataset.keys():
            self.delete(dataset[key])
Example #12
0
File: base.py Project: Fat-Zer/LDP
class DataManager(DataTable):
    """
    This class provides high level access to a database table.
    
    It loads and saves data from the table into an object or a DataSet,
    or a subclass thereof. The class of objects and data sets that
    are returned can be set using the set_object() and set_dataset()
    functions.
    
    You can request a single object (row), or you can specify a set of criteria,
    which are then translated into a WHERE clause.
    """

    def __init__(self, table_name, field_dictionary):
        DataTable.__init__(self, table_name, field_dictionary)
        self.table   = DataTable(table_name, field_dictionary)
        self.reset_cache()

    def reset_cache(self):
        """
        Initialize this data manager's object cache.

        Any contents in the cache are discarded.
        """
        self.cache        = Cache()
        self.all_cached   = 0
        self.last_synched = ''
    
    def preload(self):
        """
        Handles a client request to preload all objects from the database,
        fully populating the local cache and preparing for subsequent
        client requests.

        Preloading can result in significant performance benefits,
        because data managers who have preloaded their cache can
        fill more client requests from cache, avoiding expensive
        database accesses.

        Not all requests for preloading are honored. To be preloaded,
        a datamanager's cache size must be set to CACHE_UNLIMITED.
        Also, only one call is honored. Subsequent calls are
        silently ignored. This makes it safe and efficient for
        clients to request preloading, without needing to know
        whether or not the data manager has already been preloaded.
        """
        if self.all_cached==1:
            return

        # FIXME: Also try to preload caches that are not unlimited,
        # but are large enough to hold all existing objects.
        # Use a SQL COUNT(*) function to make this determination,
        # so we don't waste lots of time attempting to preload
        # a cache that cannot be preloaded.
        if self.cache.size <> CACHE_UNLIMITED:
            return
        #print 'Preloading ' + self.table.name
        self.get_all()
        #i18n_table = self.table.name + '_i18n'
        #if self.dms.has_key(i18n_table):
        #    i18n_dm = self.dms[i18n_table]
        #    i18n_dm.preload()
        
    def get_by_id(self, id):
        """
        Returns an individual object whose primary key field matches
        the specified id.

        If the object is in the cache, the cached object is returned.
        Objects which have more than one primary key field cannot be
        reliably retrieved using this function. In this event, only the
        first matching object will be returned.
        """
        
        object = self.cache.get_by_key(id)
        if object==None:
            data_field = self.table.id_field()
            sql = self.table.select + ' WHERE ' + data_field.field_name + '=' + data_field.attr_to_field(id)
            cursor = db.select(sql)
            row = cursor.fetchone()
            if row==None: return
            object = self.row_to_object(row)
        return object

    def get_by_keys(self, filters):
        """
        Returns all objects which match the supplied filters.
        """
        if self.cache.filled==1:
            self.all_cached = 0
        if self.all_cached==1:
            return self.get_cached_by_keys(filters)
        else:
            sql = self.filters_to_sql(filters)
            return self.get_sql(sql)

    def get_cached_by_keys(self, filters):
        """
        This private function fills keyed requests directly from the
        object cache.
        
        No checking is performed to determine whether the cache contains
        all objects which fit the request. Therefore, this function
        should only be called by data managers whose caches are preloaded.
        See the preload() function for more information on preloading.
        """
        sql = self.filters_to_sql(filters)
        function_text = self.filters_to_function(filters)
        print 'Function text: '
        print function_text
        code = compile(function_text, '<string>', 'exec')
        print 'Code: ' + str(code)
        print 'Code has %s arguments.' % code.co_argcount

        self.test_object_filters.im_func.func_code = code
        #print 'Method code: ' + str(self.test_object_filters.im_func.func_code)
        good_keys = filter(self.test_object_filters, self.cache.keys())
        print 'Good keys: ' + str(good_keys)
        dataset = self.new_dataset()
        for key in good_keys:
            dataset[key] = self.cache[key]
        return dataset

    def test_object_filters(self, key):
        return 1
    
    def filters_to_function(self, filters):
        """
        Converts a list of filters into a Python function which tests
        an object to see if it matches the filters.

        Precompiling filter tests speeds up key filtering enormously.
        
        The generated function accepts a single parameter, "key".
        It retrieves the object with that key in the object
        cache and tests for a match. If the object matches all
        the filters, the generated function returns 1.
        Otherwise, it returns 0.
        """
        code = WOStringIO()
        code.write('def test_cached_object(key):\n')
        code.write('    object = self.cache[key]\n')
        for filter in filters:
            attribute, operator, value = filter
            test_value = repr(value)
            code.write('    obj_value = object.%s\n' % (attribute))
            if operator.upper()=='LIKE':
                code.write('    if %s > len(%s): return 0\n' % (len(value), obj_value))
                code.write('    return (%s <> obj_value.upper()[:%s])\n' % (test_value.upper(), len(value)))
            elif operator in ['<>', '<', '<=', '=', '>=', '>']:
                if operator=='=':
                    operator = '=='
                code.write('    return (object.%s %s %s)\n' % (attribute, operator, repr(value)))
            else:
                raise UnknownOperator('Unrecognized operator: %s' % (operator))

        return code.get_value()
            
    def filters_to_sql(self, filters):
        """
        Converts a list of filters into the SQL statement which will
        retrieve matching records from the database.
        """
        wheres = []
        for filter in filters:
            attribute, operator, value = filter
            field = self.table.fields.find_attribute(attribute)
            if operator.upper()=='LIKE':
                wheres.append('upper(' + field_name + ') LIKE ' + field.attr_to_field(value.upper() + '%'))
            else:
                wheres.append(field.field_name + operator + field.attr_to_field(value))
        where = ' WHERE ' + string.join(wheres, ' AND ')
        return self.table.select + where

    def get_all(self):
        """
        Returns a set of all objects managed by this data manager.

        If the data manager's cache proves sufficient to cache all objects,
        the cache will subsequently be considered preloaded, i.e.,
        subsequent calls to get_by_keys() will be served directly from the
        cache, bypassing expensive database accesses. See the preload()
        function for more information on preloading.
        """
        if self.cache.filled==1:
            self.all_cached = 0
        if self.all_cached==0:
            #print 'Loading all of ' + self.table.name + ' into cache.'
            set = self.get_sql(self.table.select)
            if self.cache.filled==0:
                self.all_cached = 1
            return set
        return self.get_cached()

    def synch(self):
        """
        Synchronize objects in the object cache with the database.

        Objects which have been deleted in the database are removed
        from the object cache.
        
        Objects which are out of synch with their database record
        have their attribute set to match the data in the database.
        """
        #print 'Synchronizing ' + self.table.name + ' with database'
        last_synched = self.last_synched        # Remember this, because we're about to overwrite it.
        self.last_synched = now_string()

        # Delete any newly deleted objects.
        sql = 'SELECT identifier FROM deleted WHERE table_name=' + wsq(self.table.name) + ' AND deleted >= ' + wsq(last_synched)
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row==None: break
            
            # Load keys for the deleted object
            object = self.new_object()
            if len(self.table.key_list)==1:
                field = self.table.fields[self.table.key_list[0]]
                value = field.field_to_attr(row[0])
                setattr(object, field.attribute, value)
            else:
                values = row[0].split()
                for key in self.table.key_list:
                    field = self.table.fields[key]
                    value = field.field_to_attr(values[field.index])
                    setattr(object, field.attribute, value)
            object.key = self.table.get_key(object)

            #print 'Deleting from ' + self.table.name + ' cache: ' + str(value)
            self.cache.delete(object)

            # FIXME: Delete the object from all data sets which contain it!

        # Update any newly updated objects.
        sql = self.table.select + ' WHERE updated >= ' + wsq(last_synched)
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row==None: break
            key = self.table.get_row_key(row)
            if self.cache.has_key(key):
                object = self.cache[key]
                self.table.load_row(object, row)
                #print 'Updating in ' + self.table.name + ' cache: ' + str(object.key)
            else:
                object = self.row_to_object(row)
                self.cache.add(object)
                #print 'Adding in ' + self.table.name + ' cache: ' + str(object.key)

                # FIXME: Add the object to all data sets whose filters it matches.

    def get_cached(self):
        """
        Returns a dataset containing all objects in the object cache.
        """
        #print 'Pulling ' + self.table.name + ' from cache.'
        dataset = self.new_dataset()
        for key in self.cache.keys():
            dataset[key] = self.cache[key]
        return dataset
        
    def get_sql(self, sql):
        """
        Accepts a SQL statement, instantiates the corresponding objects from the
        database, and stores those objects in the data cache if possible.
        """
        #print 'Cache miss, loading: ' + self.table.name
        dataset = self.new_dataset()
        cursor = db.select(sql)
        while (1):
            row = cursor.fetchone()
            if row==None: break
            object = self.row_to_object(row)
            dataset[object.key] = object
            self.cache.add(object)
        return dataset

    def set_object_class(self, object_class):
        self.object_class = object_class
        
    def set_dataset_class(self, dataset_class):
        self.dataset_class = dataset_class

    def new_object(self):
        object = self.object_class(self.dms, self)
        for key in self.table.fields.keys():
            field = self.table.fields[key]
            setattr(object, field.attribute, field.get_default())
        object.changed = 0
        object.in_database = 0
        return object

    def new_dataset(self):
        return self.dataset_class(self)
    
    def row_to_object(self, row):
        object = self.new_object()
        self.table.load_row(object, row)
        return object

    def add(self, object):
        self.save(object)

    def save(self, object):
        object.key = self.table.get_key(object) # New objects need their key calculated.
        if object.changed==0 and object.in_database==0:
            return
        if object.in_database==0:
            field_list = []
            value_list = []
            for key in self.table.field_list:
                field = self.table.fields[key]
                if field.data_type=='created':  # The database is responsible for setting the timestamp.
                    continue
                if field.data_type=='sequence': # When inserting, always increment the value.
                    new_id = db.next_id(self.name, field.field_name)
                    setattr(object, field.attribute, new_id)
                value = field.attr_to_field(getattr(object, field.attribute))
                field_list.append(field.field_name)
                value_list.append(value)
            sql = 'INSERT INTO %s (%s) VALUES (%s)' % (self.table.name, string.join(field_list, ', '), string.join(value_list, ', '))
        else:
            update_list = []
            where_list = []
            for key in self.table.field_list:
                field = self.table.fields[key]
                if field.data_type=='created':
                    continue
                if field.data_type=='updated':
                    value = wsq(now_string())
                else:
                    value = field.attr_to_field(getattr(object, field.attribute))
                update_list.append(field.field_name + '=' + value)
                if field.key_field==1:
                    where_list.append(field.field_name + '=' + value)
            sql = 'UPDATE %s SET %s WHERE %s' % (self.table.name, string.join(update_list, ', '), string.join(where_list, ' AND '))
#            print sql
        db.runsql(sql)
        db.commit()
        self.cache.add(object)
        object.in_database = 1
        object.changed = 0

    def delete(self, object):
        if object.in_database==0:
            return
        self.cache.delete(object)
        wheres = []
        for key in self.table.key_list:
            data_field = self.table.fields[key]
            value = data_field.attr_to_field(getattr(object, data_field.attribute))
            wheres.append(data_field.field_name + '=' + value)
        where = ' WHERE ' + string.join(wheres, ' AND ')
        sql = 'DELETE FROM %s %s' % (self.table.name, where)
        db.runsql(sql)
        db.commit()
        sql = 'INSERT INTO deleted (table_name, identifier) VALUES (%s, %s)' % (wsq(self.table.name), wsq(str(object.key)))
        db.runsql(sql)
        db.commit()

    def delete_by_keys(self, filters):
        dataset = self.get_by_keys(filters)
        for key in dataset.keys():
            object = dataset[key]
            self.delete(object)
    
    def clear(self, dataset):
        for key in dataset.keys():
            self.delete(dataset[key])