How can I improve this code (elegance, best practices, etc)?
""" Simple in-memory database as a response to the Thumbtack coding challenge. """
class SimpleDb(object):
""" Simple in-memory database as a response to the Thumbtack coding challenge. """
def __init__(self):
""" Initialize SimpleDb instance. """
self.__db = {}
self.__num_equal_to_cache = {}
self.__transactions = [] # Stores previous values to allow rollback
def assign(self, name, value):
""" Sets value of name to value. Inserts name into database if it doesn't already exist. """
current_value = self.get(name)
if current_value == value:
return
self.__update_num_equal_to(current_value, value)
self.__update_current_transaction(name, current_value)
self.__db[name] = value
def get(self, name):
""" Returns value of name if it exists in the database, otherwise returns None. """
return self.__db[name] if name in self.__db else None
def get_num_equal_to(self, value):
""" Returns number of entries in the database that have the specified value. """
return self.__num_equal_to_cache[value] if value in self.__num_equal_to_cache else 0
def unset(self, name):
""" Removes name from database if it's present. """
current_value = self.__db.pop(name, None)
if current_value is None:
return
self.__update_num_equal_to(current_value)
self.__update_current_transaction(name, current_value)
def begin(self):
""" Opens transaction block. """
self.__transactions += [{}]
def rollback(self):
"""
Reverts database to its state before most current transaction.
Returns True on success, returns False if there aren't any open transactions.
"""
if not self.__transactions:
return False
for name, value in self.__transactions.pop().iteritems():
current_value = self.get(name)
if current_value == value:
continue
self.__update_num_equal_to(current_value, value)
if value is None:
del self.__db[name]
else:
self.__db[name] = value
return True
def commit(self):
"""
Commits all transactions to database. Returns True on success,
returns False if there aren't any open transactions.
"""
if not self.__transactions:
return False
self.__transactions = []
return True
def __update_num_equal_to(self, current_value, new_value=None):
"""
Decrements by one the number items present with current_value (if current_value
is not equal to None) and increments by one the number present with new_value
(if new_value is not equal to None).
"""
for amount_to_add, value in [(-1, current_value), (1, new_value)]:
if value is not None:
self.__num_equal_to_cache.setdefault(value, 0)
self.__num_equal_to_cache[value] += amount_to_add
def __update_current_transaction(self, name, value):
"""
Stores current value of name if not already stored to most recent transaction
(if any transactions open) to enable restoration of previous state on rollback.
"""
if self.__transactions and name not in self.__transactions[-1]:
self.__transactions[-1][name] = value
def display(value, default=None):
"""
Prints value to stdout. If value is None and a default value is
specified (and not None), then the default value is printed instead. Otherwise
the None value is printed.
"""
if value is None and default is not None:
value = default
print value
OPS = {
'GET': (2, lambda db, name: display(db.get(name), "NULL")),
'NUMEQUALTO': (2, lambda db, value: display(db.get_num_equal_to(value))),
'UNSET': (2, lambda db, name: db.unset(name)),
'BEGIN': (1, lambda db: db.begin()),
'ROLLBACK': (1, lambda db: db.rollback() or display("NO TRANSACTION")),
'COMMIT': (1, lambda db: db.commit() or display("NO TRANSACTION")),
'END': (1, lambda db: False),
'SET': (3, lambda db, name, value: db.assign(name, value)),
}
def process_command(simpleDb, command):
"""
Parses string commands and applies them to the database.
Returning False indicates that no more commands should be passed in.
"""
command = command.split()
opcode = command.pop(0).upper() if len(command) > 0 else None
if opcode is None or opcode not in OPS or len(command) != (OPS[opcode][0] - 1):
print "INVALID COMMAND"
elif 'END' == opcode:
return False
else:
OPS[opcode][1](simpleDb, *command)
return True
def run():
""" Reads database command from the command line and passes it through for processing. """
# BEGIN \n SET a 30 \n BEGIN \n SET a 40 \n COMMIT \n GET a \n ROLLBACK \n END
simpleDb = SimpleDb()
while process_command(simpleDb, raw_input()):
pass
run()