Low Concurrency Counter
Sun, 30 October 2016
__init__.py
"""Low Concurrency Counters A simple counter library """ from google.appengine.ext import ndb class LowConcurrencyCounterService(object): @staticmethod def get(name): """Retrieve a single named counter :param str|unicode name: Name of counter to be retrieved :rtype: LowConcurrencyCounterModel """ return LowConcurrencyCounterService.__get_or_insert(name) @staticmethod def get_multi(names): """Retrieve multiple named counters :param list: Names of counters to be retrieved :rtype: list """ if len(names) < 1: return [] return LowConcurrencyCounterService.__get_or_insert_multi(names) @staticmethod def delete(name): """Delete a single named counter :param str|unicode name: Name of counter to be deleted :rtype: None """ model = LowConcurrencyCounterService.__get_or_insert(name, False) model.key.delete() return None @staticmethod def reset(name): """Reset a given counter to 0. if the counter does not exist, a new counter will be created. :param str|unicode name: Name of counter to be reset. :rtype: LowConcurrencyCounterModel """ model = LowConcurrencyCounterService.__get_or_insert(name) if model is None: return None if model.count == 0: return model model.count = 0 model.put() return model @staticmethod def incr(name, delta=1): """Increment a named counter by the delta :param str name: Name of counter to be incremented. :param int delta: Non-negative integer value (int or long) to increment key by, defaulting to 1. :rtype: LowConcurrencyCounterModel Raises: ValueError: If number is negative. TypeError: If delta isn't an int or long. """ if not isinstance(delta, (int, long)): raise TypeError('Delta must be an integer or long, received %r' % delta) if delta < 0: raise ValueError('Delta must not be negative.') model = LowConcurrencyCounterService.__get_or_insert(name) if model is None: return None model.count += delta model.put() return model @staticmethod def decr(name, delta=1): """Decrement a named counter by the delta. :param str name: Name of counter to be decremented. :param int delta: Non-negative integer value (int or long) to decrement key by, defaulting to 1. :rtype: LowConcurrencyCounterModel :raises: ValueError Raises: ValueError: If number is negative. TypeError: If delta isn't an int or long. """ if not isinstance(delta, (int, long)): raise TypeError('Delta must be an integer or long, received %r' % delta) if delta < 0: raise ValueError('Delta must not be negative.') model = LowConcurrencyCounterService.__get_or_insert(name) if model is None: return None model.count -= delta model.put() return model @staticmethod def __get_or_insert(name, use_get_or_insert=True): """Get or create a named counter :param str name: Name of counter to be processed. :param bool use_get_or_insert: Optional, default True. Whether to create if not exists. :rtype: LowConcurrencyCounterModel """ if use_get_or_insert is False: return LowConcurrencyCounterModel.get_by_id(unicode(name)) return LowConcurrencyCounterModel.get_or_insert( unicode(name), count=0 ) @staticmethod def __get_or_insert_multi(names): """Get or create multiple named counters :param list names: Names of counters to be processed. :rtype: LowConcurrencyCounterModel """ keys = [] for n in names: keys.append(ndb.Key(LowConcurrencyCounterModel, unicode(n))) final = [] records = ndb.get_multi(keys) # type: list for record in records: if record is not None: names.remove(record.get_name()) final.append(record) create_entities = [] for n in names: create_entities.append(LowConcurrencyCounterModel(id=unicode(n))) if len(create_entities) > 0: ndb.put_multi(create_entities) final.extend(create_entities) return final class LowConcurrencyCounterModel(ndb.Model): count = ndb.IntegerProperty(indexed=False, name=u'c', default=0) def get_count(self): """ :rtype: unicode """ return self.count def get_name(self): """ :rtype: unicode """ return unicode(self.key.id())
README.md
# Low Concurrency Counters ## About A simple library to manage low concurrency counters by utilising a lightweight datastore model. ## Usage ### Code: ``` from low_concurrency_counters import LowConcurrencyCounterService import logging logging.info("Get counter") counter = LowConcurrencyCounterService.get("foo") logging.info(counter.get_name()) logging.info(counter.get_count()) logging.info("Increment counter by 1") counter = LowConcurrencyCounterService.incr("foo") logging.info(counter.get_count()) logging.info("Increment counter by 2") counter = LowConcurrencyCounterService.incr("foo", 2) logging.info(counter.get_count()) logging.info("Decrement counter by 1") counter = LowConcurrencyCounterService.decr("foo") logging.info(counter.get_count()) logging.info("Decrement counter by 2") counter = LowConcurrencyCounterService.decr("foo", 2) logging.info(counter.get_count()) logging.info("Increment counter by 2 again") counter = LowConcurrencyCounterService.incr("foo", 2) logging.info(counter.get_count()) logging.info("Get multiple counters") counters = LowConcurrencyCounterService.get_multi(["foo", "bar"]) for counter in counters: logging.info(counter.get_name() + " : " + str(counter.get_count())) logging.info("Reset counter to 0") counter = LowConcurrencyCounterService.reset("foo") logging.info(counter.get_count()) logging.info("Delete counter") LowConcurrencyCounterService.delete("foo") LowConcurrencyCounterService.delete("bar") ``` ### Result: ``` Get counter INFO] foo INFO] 0 INFO] Increment counter by 1 INFO] 1 INFO] Increment counter by 2 INFO] 3 INFO] Decrement counter by 1 INFO] 2 INFO] Decrement counter by 2 INFO] 0 INFO] Increment counter by 2 again INFO] 2 INFO] Get multiple counters INFO] foo : 2 INFO] bar : 0 INFO] Reset counter to 0 INFO] 0 INFO] Delete counter # No return from this. ```