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.
```