All db operations now should go thru Database object

This commit is contained in:
Václav 'Ax' Hůla 2012-10-27 23:20:43 +02:00
parent ffa119e7f0
commit a4dbf3b77c
5 changed files with 128 additions and 100 deletions

View file

@ -5,6 +5,8 @@ import psycopg2
from PySide import QtCore, QtGui, QtDeclarative from PySide import QtCore, QtGui, QtDeclarative
from brmbar import Database
import brmbar import brmbar
@ -173,7 +175,7 @@ class ShopAdapter(QtCore.QObject):
db.commit() db.commit()
return balance return balance
db = psycopg2.connect("dbname=brmbar") db = Database.Database("dbname=brmbar")
shop = brmbar.Shop.new_with_defaults(db) shop = brmbar.Shop.new_with_defaults(db)
currency = shop.currency currency = shop.currency
db.commit() db.commit()

View file

@ -17,9 +17,7 @@ class Account:
@classmethod @classmethod
def load_by_barcode(cls, db, barcode): def load_by_barcode(cls, db, barcode):
with closing(db.cursor()) as cur: res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
cur.execute("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
res = cur.fetchone()
if res is None: if res is None:
return None return None
id = res[0] id = res[0]
@ -29,19 +27,15 @@ class Account:
def load(cls, db, id = None, name = None): def load(cls, db, id = None, name = None):
""" Constructor for existing account """ """ Constructor for existing account """
if id is not None: if id is not None:
with closing(db.cursor()) as cur: name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id])
cur.execute("SELECT name FROM accounts WHERE id = %s", [id]) name = name[0]
name = cur.fetchone()[0]
elif name is not None: elif name is not None:
with closing(db.cursor()) as cur: id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name])
cur.execute("SELECT id FROM accounts WHERE name = %s", [name]) id = id[0]
id = cur.fetchone()[0]
else: else:
raise NameError("Account.load(): Specify either id or name") raise NameError("Account.load(): Specify either id or name")
with closing(db.cursor()) as cur: currid, acctype = db.execute_and_fetch("SELECT currency, acctype FROM accounts WHERE id = %s", [id])
cur.execute("SELECT currency, acctype FROM accounts WHERE id = %s", [id])
currid, acctype = cur.fetchone()
currency = Currency.load(db, id = currid) currency = Currency.load(db, id = currid)
return cls(db, name = name, id = id, currency = currency, acctype = acctype) return cls(db, name = name, id = id, currency = currency, acctype = acctype)
@ -49,17 +43,15 @@ class Account:
@classmethod @classmethod
def create(cls, db, name, currency, acctype): def create(cls, db, name, currency, acctype):
""" Constructor for new account """ """ Constructor for new account """
with closing(db.cursor()) as cur: id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype])
cur.execute("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype]) id = id[0]
id = cur.fetchone()[0]
return cls(db, name = name, id = id, currency = currency, acctype = acctype) return cls(db, name = name, id = id, currency = currency, acctype = acctype)
def balance(self): def balance(self):
with closing(self.db.cursor()) as cur: debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
cur.execute("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit']) debit = debit[0] or 0
debit = cur.fetchone()[0] or 0 credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
cur.execute("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit']) credit = credit[0] or 0
credit = cur.fetchone()[0] or 0
return debit - credit return debit - credit
def balance_str(self): def balance_str(self):
@ -76,15 +68,12 @@ class Account:
def _transaction_split(self, transaction, side, amount, memo): def _transaction_split(self, transaction, side, amount, memo):
""" Common part of credit() and debit(). """ """ Common part of credit() and debit(). """
with closing(self.db.cursor()) as cur: self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
cur.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
def add_barcode(self, barcode): def add_barcode(self, barcode):
with closing(self.db.cursor()) as cur: self.db.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
cur.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
self.db.commit() self.db.commit()
def rename(self, name): def rename(self, name):
with closing(self.db.cursor()) as cur: self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
cur.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
self.name = name self.name = name

View file

@ -20,13 +20,11 @@ class Currency:
def load(cls, db, id = None, name = None): def load(cls, db, id = None, name = None):
""" Constructor for existing currency """ """ Constructor for existing currency """
if id is not None: if id is not None:
with closing(db.cursor()) as cur: name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
cur.execute("SELECT name FROM currencies WHERE id = %s", [id]) name = name[0]
name = cur.fetchone()[0]
elif name is not None: elif name is not None:
with closing(db.cursor()) as cur: id = db.execute_and_fetch("SELECT id FROM currencies WHERE name = %s", [name])
cur.execute("SELECT id FROM currencies WHERE name = %s", [name]) id = id[0]
id = cur.fetchone()[0]
else: else:
raise NameError("Currency.load(): Specify either id or name") raise NameError("Currency.load(): Specify either id or name")
return cls(db, name = name, id = id) return cls(db, name = name, id = id)
@ -34,26 +32,22 @@ class Currency:
@classmethod @classmethod
def create(cls, db, name): def create(cls, db, name):
""" Constructor for new currency """ """ Constructor for new currency """
with closing(db.cursor()) as cur: id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name])
cur.execute("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name]) id = id[0]
id = cur.fetchone()[0]
return cls(db, name = name, id = id) return cls(db, name = name, id = id)
def rates(self, other): def rates(self, other):
""" Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency): """ Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency):
$buy is the price of $self in means of $other when buying it (into brmbar) $buy is the price of $self in means of $other when buying it (into brmbar)
$sell is the price of $self in means of $other when selling it (from brmbar) """ $sell is the price of $self in means of $other when selling it (from brmbar) """
with closing(self.db.cursor()) as cur:
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id]) res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id])
res = cur.fetchone()
if res is None: if res is None:
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name())
buy_rate, buy_rate_dir = res buy_rate, buy_rate_dir = res
buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id]) res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id])
res = cur.fetchone()
if res is None: if res is None:
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name()) raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name())
sell_rate, sell_rate_dir = res sell_rate, sell_rate_dir = res
@ -62,11 +56,9 @@ class Currency:
return (buy, sell) return (buy, sell)
def convert(self, amount, target): def convert(self, amount, target):
with closing(self.db.cursor()) as cur: res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id])
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id])
res = cur.fetchone()
if res is None: if res is None:
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + other.name()) raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name())
rate, rate_dir = res rate, rate_dir = res
if rate_dir == "source_to_target": if rate_dir == "source_to_target":
resamount = amount * rate resamount = amount * rate
@ -78,8 +70,6 @@ class Currency:
return "{:.2f} {}".format(amount, self.name) return "{:.2f} {}".format(amount, self.name)
def update_sell_rate(self, target, rate): def update_sell_rate(self, target, rate):
with closing(self.db.cursor()) as cur: self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"])
cur.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"])
def update_buy_rate(self, source, rate): def update_buy_rate(self, source, rate):
with closing(self.db.cursor()) as cur: self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"])
cur.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"])

View file

@ -0,0 +1,50 @@
"""self-reconnecting database object"""
import psycopg2
from contextlib import closing
import time
class Database:
"""self-reconnecting database object"""
def __init__(self, dsn):
self.db_conn = psycopg2.connect(dsn)
self.dsn = dsn
def execute(self, query, attrs = None):
"""execute a query and return one result"""
with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs)
def execute_and_fetch(self, query, attrs = None):
"""execute a query and return one result"""
with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs)
return cur.fetchone()
def execute_and_fetchall(self, query, attrs = None):
"""execute a query and return all results"""
with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs)
return cur.fetchall()
def _execute(self, cur, query, attrs, level=1):
"""execute a query, and in case of OperationalError (db restart)
reconnect to database. Recurses with increasig pause between tries"""
try:
if attrs is None:
cur.execute(query)
else:
cur.execute(query, attrs)
return cur
except psycopg2.OperationalError as error:
print("Sleeping: level %s (%s) @%s" % (
level, error, time.strftime("%Y%m%d %a %I:%m %p")
))
time.sleep(2 ** level)
self.db_conn = psycopg2.connect(self.dsn)
cur = self.db_conn.cursor() #how ugly is this?
return self._execute(cur, query, attrs, level+1)
def commit(self):
"""passes commit to db"""
self.db_conn.commit()

View file

@ -82,14 +82,12 @@ class Shop:
self.db.commit() self.db.commit()
def _transaction(self, responsible = None, description = None): def _transaction(self, responsible = None, description = None):
with closing(self.db.cursor()) as cur: transaction = self.db.execute_and_fetch("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
cur.execute("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
[responsible.id if responsible else None, description]) [responsible.id if responsible else None, description])
transaction = cur.fetchone()[0] transaction = transaction[0]
return transaction return transaction
def credit_balance(self): def credit_balance(self):
with closing(self.db.cursor()) as cur:
# We assume all debt accounts share a currency # We assume all debt accounts share a currency
sumselect = """ sumselect = """
SELECT SUM(ts.amount) SELECT SUM(ts.amount)
@ -97,20 +95,19 @@ class Shop:
LEFT JOIN transaction_splits AS ts ON a.id = ts.account LEFT JOIN transaction_splits AS ts ON a.id = ts.account
WHERE a.acctype = %s AND ts.side = %s WHERE a.acctype = %s AND ts.side = %s
""" """
cur.execute(sumselect, ["debt", 'debit']) cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit'])
debit = cur.fetchone()[0] or 0 debit = cur[0] or 0
cur.execute(sumselect, ["debt", 'credit']) credit = self.db.execute_and_fetch(sumselect, ["debt", 'credit'])
credit = cur.fetchone()[0] or 0 credit = credit[0] or 0
return debit - credit return debit - credit
def credit_negbalance_str(self): def credit_negbalance_str(self):
return self.currency.str(-self.credit_balance()) return self.currency.str(-self.credit_balance())
def inventory_balance(self): def inventory_balance(self):
balance = 0 balance = 0
with closing(self.db.cursor()) as cur:
# Each inventory account has its own currency, # Each inventory account has its own currency,
# so we just do this ugly iteration # so we just do this ugly iteration
cur.execute("SELECT id FROM accounts WHERE acctype = %s", ["inventory"]) cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s", ["inventory"])
for inventory in cur: for inventory in cur:
invid = inventory[0] invid = inventory[0]
inv = Account.load(self.db, id = invid) inv = Account.load(self.db, id = invid)
@ -124,9 +121,9 @@ class Shop:
return self.currency.str(self.inventory_balance()) return self.currency.str(self.inventory_balance())
def account_list(self, acctype): def account_list(self, acctype):
"""list all accounts (people or items, as per acctype)"""
accts = [] accts = []
with closing(self.db.cursor()) as cur: cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s ORDER BY name ASC", [acctype])
cur.execute("SELECT id FROM accounts WHERE acctype = %s ORDER BY name ASC", [acctype])
for inventory in cur: for inventory in cur:
accts += [ Account.load(self.db, id = inventory[0]) ] accts += [ Account.load(self.db, id = inventory[0]) ]
return accts return accts