async def sanity_check(self): async with self.pool.acquire() as con: rows = await con.fetch( "SELECT * FROM transactions WHERE (last_status = 'unconfirmed' OR last_status IS NULL) " "AND created < (now() AT TIME ZONE 'utc') - interval '5 minutes'" ) for row in rows: # check if the transaction is still in the network tx = await self.eth.eth_getTransactionByHash( row['transaction_hash']) if tx is None: # the transaction no longer exists on the network await self.send_transaction_notifications({ 'hash': row['transaction_hash'], 'to': row['to_address'], 'from': row['from_address'], 'blockNumber': None, 'value': row['value'], 'nonce': row['nonce'], 'gas': row['estimated_gas_cost'], 'gasPrice': '0x1', 'error': True }) else: # check if the transactions has actually been confirmed if tx['blockNumber'] is not None: await self.send_transaction_notifications(tx) else: # update the nonce if it doesn't match what is in the database # (NOTE: this shouldn't really be the case ever, this is mainly # for checking on tx's from before the nonce was stored) if parse_int(tx['nonce']) != row['nonce']: log.info( "Fixing stored nonce value for tx: {} (old: {}, new: {})" .format(tx['hash'], row['nonce'], parse_int(tx['nonce']))) async with self.pool.acquire() as con: await con.execute( "UPDATE transactions SET nonce = $1 WHERE transaction_hash = $2", parse_int(tx['nonce']), row['transaction_hash']) # log that there is an error log.error( "Found long standing unconfirmed transaction: {}". format(tx['hash']))
async def test_get_balance(self): addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb' val = 761751855997712 await self.faucet(addr, val) resp = await self.fetch('/balance/{}'.format(addr)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), val) self.assertEqual(parse_int(data['unconfirmed_balance']), val)
async def test_get_balance_with_unconfirmed_txs(self): addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb' val = 761751855997712 await self.faucet(addr, val) async with self.pool.acquire() as con: await con.execute("INSERT INTO transactions VALUES ('0x2f321aa116146a9bc62b61c76508295f708f42d56340c9e613ebfc27e33f240c', $1, $2, 0, $3)", FAUCET_ADDRESS, addr, val) resp = await self.fetch('/balance/{}'.format(addr)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), val) self.assertEqual(parse_int(data['unconfirmed_balance']), val * 2)
async def get_reports(request, conf, current_user): page = parse_int(request.args.get('page', None)) or 1 if page < 1: page = 1 limit = 10 offset = (page - 1) * limit sql = ("SELECT * FROM reports " "ORDER BY report_id DESC " "OFFSET $1 LIMIT $2") args = [offset, limit] count_sql = ("SELECT COUNT(*) FROM reports") count_args = [] async with conf.db.id.acquire() as con: rows = await con.fetch(sql, *args) count = await con.fetchrow(count_sql, *count_args) reports = [] for row in rows: async with conf.db.id.acquire() as con: reporter = await con.fetchrow( "SELECT * FROM users WHERE token_id = $1", row['reporter_token_id']) reportee = await con.fetchrow( "SELECT * FROM users WHERE token_id = $1", row['reportee_token_id']) reporter = fix_avatar_for_user(conf.urls.id, dict(reporter)) reportee = fix_avatar_for_user(conf.urls.id, dict(reportee)) reports.append({ 'reporter': reporter, 'reportee': reportee, 'details': row['details'], 'date': row['date'] }) total_pages = (count['count'] // limit) + (0 if count['count'] % limit == 0 else 1) def get_qargs(page=page, as_list=False, as_dict=False): qargs = {'page': page} if as_dict: return qargs if as_list: return qargs.items() return urlencode(qargs) return html(await env.get_template("reports.html").render_async( reports=reports, current_user=current_user, environment=conf.name, page="reports", total=count['count'], total_pages=total_pages, current_page=page, get_qargs=get_qargs))
async def get_txs(request, conf, user): page = parse_int(request.args.get('page', None)) or 1 if page < 1: page = 1 limit = 10 offset = (page - 1) * limit where_clause = '' filters = [ f for f in request.args.getlist('filter', []) if f in ['confirmed', 'unconfirmed', 'error'] ] if filters: where_clause = "WHERE " + " OR ".join("status = '{}'".format(f) for f in filters) if 'unconfirmed' in filters: where_clause += " OR status IS NULL" async with conf.db.eth.acquire() as con: rows = await con.fetch( "SELECT * FROM transactions {} ORDER BY created DESC OFFSET $1 LIMIT $2" .format(where_clause), offset, limit) count = await con.fetchrow( "SELECT COUNT(*) FROM transactions {}".format(where_clause)) txs = [] for row in rows: tx = dict(row) tx['from_user'] = await get_token_user_from_payment_address( conf, tx['from_address']) tx['to_user'] = await get_token_user_from_payment_address( conf, tx['to_address']) txs.append(tx) total_pages = (count['count'] // limit) + (0 if count['count'] % limit == 0 else 1) def get_qargs(page=page, filters=filters, as_list=False, as_dict=False): qargs = {'page': page} if filters: qargs['filter'] = filters if as_dict: return qargs if as_list: return qargs.items() return urlencode(qargs, True) return html(await env.get_template("txs.html").render_async( txs=txs, current_user=user, environment=conf.name, page="txs", total=count['count'], total_pages=total_pages, current_page=page, active_filters=filters, get_qargs=get_qargs))
def to_eth(wei, points=18): wei = str(parse_int(wei)) pad = 18 - len(wei) if pad < 0: eth = wei[:abs(pad)] + "." + wei[abs(pad):abs(pad) + points] else: eth = "0." + wei.zfill(18)[:points] while eth.endswith("0"): eth = eth[:-1] if eth.endswith("."): eth += "0" return eth
async def check_account_nonces(conf, dryrun=False): async with conf.db.id.acquire() as con: users = await con.fetch("SELECT token_id, payment_address FROM users") last_percent = -1 async with conf.db.eth.acquire() as con: tr = con.transaction() await tr.start() for i, user in enumerate(users): percent = int((i / len(users)) * 100) if percent != last_percent: print("Progress: {}/{} ({}%)".format(i, len(users), percent)) last_percent = percent from_address = user['payment_address'] resp = await app.http.post( conf.urls.node, headers={'Content-Type': 'application/json'}, data=json.dumps({ "jsonrpc": "2.0", "id": random.randint(0, 1000000), "method": "eth_getTransactionCount", "params": [from_address] }).encode('utf-8')) if resp.status == 200: data = await resp.json() if 'result' in data and data['result'] is not None: nonce = parse_int(data['result']) res = await con.execute( "UPDATE transactions SET last_status = 'error' WHERE from_address = $1 AND nonce >= $2 AND last_status != 'error'", from_address, nonce) if res != "UPDATE 0": print("{}|{}: {}".format(user['token_id'], from_address, res)) if dryrun: await tr.rollback() else: await tr.commit()
async def post(self): if 'reputation' not in self.application.config or 'id' not in self.application.config['reputation']: raise HTTPError(404) try: address = self.verify_request() except JSONHTTPError: raise HTTPError(404) if address != self.application.config['reputation']['id']: raise HTTPError(404) if not all(x in self.json for x in ['address', 'score', 'count']): raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]}) token_id = self.json['address'] if not validate_address(token_id): raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_address', 'message': 'Invalid Address'}]}) count = self.json['count'] count = parse_int(count) if count is None: raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_count', 'message': 'Invalid Count'}]}) score = self.json['score'] if isinstance(score, str) and validate_decimal_string(score): score = Decimal(score) if not isinstance(score, (int, float, Decimal)): raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_score', 'message': 'Invalid Score'}]}) async with self.db: await self.db.execute("UPDATE apps SET reputation_score = $1, review_count = $2 WHERE token_id = $3", score, count, token_id) await self.db.commit() self.set_status(204)
def test_parse_float_string(self): self.assertEqual(parse_int("12345.567678"), 12345)
async def create_transaction_skeleton(self, *, to_address, from_address, value=0, nonce=None, gas=None, gas_price=None, data=None): if not validate_address(from_address): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_from_address', 'message': 'Invalid From Address' }) if not validate_address(to_address): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_to_address', 'message': 'Invalid To Address' }) if value: value = parse_int(value) if value is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_value', 'message': 'Invalid Value' }) # check optional arguments if nonce is None: # check cache for nonce nonce = self.redis.get("nonce:{}".format(from_address)) if nonce: nonce = int(nonce) # get the network's value too nw_nonce = await self.eth.eth_getTransactionCount(from_address) if nonce is None or nw_nonce > nonce: # if not cached, or the cached value is lower than # the network value, use the network value! nonce = nw_nonce else: nonce = parse_int(nonce) if nonce is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_nonce', 'message': 'Invalid Nonce' }) if data is not None: if isinstance(data, int): data = hex(data) if isinstance(data, str): try: data = data_decoder(data) except binascii.Error: pass if not isinstance(data, bytes): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_data', 'message': 'Invalid Data field' }) else: data = b'' if gas is None: # if there is data the default startgas value wont be enough if data: gas = await self.eth.eth_estimateGas(from_address, to_address, nonce=nonce, data=data) else: gas = DEFAULT_STARTGAS else: gas = parse_int(gas) if gas is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_gas', 'message': 'Invalid Gas' }) if gas_price is None: gas_price = DEFAULT_GASPRICE else: gas_price = parse_int(gas_price) if gas_price is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_gas_price', 'message': 'Invalid Gas Price' }) tx = create_transaction(nonce=nonce, gasprice=gas_price, startgas=gas, to=to_address, value=value, data=data) transaction = encode_transaction(tx) return transaction
def test_parse_int_string(self): self.assertEqual(parse_int("12345"), 12345)
def test_parse_unicode(self): self.assertEqual(parse_int(u'12345'), 12345)
def test_parse_bytes(self): self.assertEqual(parse_int(b"12345"), 12345)
def test_parse_misc(self): self.assertEqual(parse_int({}), None)
def test_parse_none(self): self.assertEqual(parse_int(None), None)
def do_POST(self): # TODO: figure out why read is blocking here data = self.rfile.read(len(self.rfile.peek())) data = data.decode('utf-8') data = json.loads(data) if self.path == "/v1/tx/skel": gas_price = parse_int( data['gas_price']) if 'gas_price' in data else DEFAULT_GASPRICE gas = parse_int(data['gas']) if 'gas' in data else DEFAULT_STARTGAS nonce = parse_int(data['nonce']) if 'nonce' in data else 0 if 'value' not in data or 'from' not in data or 'to' not in data: self.write_data( 400, { 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) return value = parse_int(data['value']) to_address = data['to'] from_address = data['from'] if not validate_address(to_address): self.write_data( 400, { 'errors': [{ 'id': 'invalid_to_address', 'message': 'Invalid To Address' }] }) return if not validate_address(from_address): self.write_data( 400, { 'errors': [{ 'id': 'invalid_from_address', 'message': 'Invalid From Address' }] }) return tx = create_transaction(nonce=nonce, gasprice=gas_price, startgas=gas, to=to_address, value=value) transaction = encode_transaction(tx) self.write_data( 200, { "tx_data": { "nonce": hex(nonce), "from": from_address, "to": to_address, "value": hex(value), "startGas": hex(gas), "gasPrice": hex(gas_price) }, "tx": transaction }) elif self.path == "/v1/tx": tx = decode_transaction(data['tx']) if 'signature' in data: sig = data_decoder(data['signature']) add_signature_to_transaction(tx, sig) self.write_data(200, {"tx_hash": data_encoder(tx.hash)}) else: self.write_data(404)
async def send_transaction_notifications(self, transaction): to_address = transaction['to'] from_address = transaction['from'] token_ids = [] overwritten_tx_hash = None overwritten_tx_hash_to_address = None overwritten_tx_token_ids = [] sender_token_id = None async with self.pool.acquire() as con: # find if we have a record of this tx by checking the from address and nonce db_txs = await con.fetch( "SELECT * FROM transactions WHERE " "from_address = $1 AND nonce = $2", from_address, parse_int(transaction['nonce'])) if len(db_txs) > 1: # see if one has the same hash db_tx = await con.fetchrow( "SELECT * FROM transactions WHERE " "from_address = $1 AND nonce = $2 AND transaction_hash = $3 AND (last_status != $4 OR last_status IS NULL)", from_address, parse_int(transaction['nonce']), transaction['hash'], 'error') if db_tx is None: # find if there are any that aren't marked as error no_error = await con.fetch( "SELECT * FROM transactions WHERE " "from_address = $1 AND nonce = $2 AND transaction_hash != $3 AND (last_status != $4 OR last_status IS NULL)", from_address, parse_int(transaction['nonce']), transaction['hash'], 'error') if len(no_error) == 1: db_tx = no_error[0] elif len(no_error) != 0: log.warning( "Multiple transactions from '{}' exist with nonce '{}' in unknown state" ) elif len(db_txs) == 1: db_tx = db_txs[0] else: db_tx = None to_ids = await con.fetch( "SELECT token_id FROM notification_registrations WHERE eth_address = $1", to_address) to_ids = [row['token_id'] for row in to_ids] token_ids.extend(to_ids) from_ids = await con.fetch( "SELECT token_id FROM notification_registrations WHERE eth_address = $1", from_address) from_ids = [row['token_id'] for row in from_ids] token_ids.extend(from_ids) # check if we have this transaction saved in the database with # a specific token_id for the sender if db_tx: sender_token_id = db_tx['sender_token_id'] # make sure the tx hash we have already matches this if transaction['hash'] != db_tx['transaction_hash']: # only worry about this if it wasn't set as an error already if db_tx['last_status'] != 'error': log.warning("found overwritten transaction!") log.warning("tx from: {}".format(from_address)) log.warning("nonce: {}".format( parse_int(transaction['nonce']))) log.warning("old tx hash: {}".format( db_tx['transaction_hash'])) log.warning("new tx hash: {}".format( transaction['hash'])) log.warning("old tx status: {}".format( db_tx['last_status'])) # if not we have an overwrite overwritten_tx_hash = db_tx['transaction_hash'] overwritten_tx_hash_to_address = db_tx['to_address'] if to_address != overwritten_tx_hash_to_address: old_to_ids = await con.fetch( "SELECT token_id FROM notification_registrations WHERE eth_address = $1", db_tx['to_address']) old_to_ids = [ row['token_id'] for row in old_to_ids ] overwritten_tx_token_ids.extend(old_to_ids) else: overwritten_tx_token_ids.extend(to_ids) overwritten_tx_token_ids.extend(from_ids) await con.execute( "UPDATE transactions SET error = 1, last_status = 'error' WHERE transaction_hash = $1", db_tx['transaction_hash']) # reset db_tx so we store the new tx later on db_tx = None else: new_status = 'error' if 'error' in transaction else ( 'confirmed' if transaction['blockNumber'] is not None else 'unconfirmed') # check last_status to see if we need to send this as a pn or not # and update the status if db_tx['last_status'] == new_status: # we've already sent a notification for this state # so there's no need to send another return log.info("setting {} status on transaction: {}".format( new_status, transaction['hash'])) await con.execute( "UPDATE transactions SET {}" "last_status = $1 " "WHERE transaction_hash = $2".format( "confirmed = (now() AT TIME ZONE 'utc'), " if new_status == 'confirmed' else ''), new_status, transaction['hash']) # if there are interested parties in the new tx, add it to the database if db_tx is None and (token_ids or any( addr in self.callbacks for addr in [to_address, from_address])): log.info("storing info about new interesting transaction") log.warning("tx from: {}".format(from_address)) log.warning("nonce: {}".format(transaction['nonce'])) log.warning("new tx hash: {}".format(transaction['hash'])) # this could potentially be an overwrite of an overwrite putting the original # transaction back in as a non-error, in this case the last_status value is # simply updated from the original tx await con.execute( "INSERT INTO transactions " "(transaction_hash, from_address, to_address, nonce, value, estimated_gas_cost, sender_token_id, last_status) " "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) " "ON CONFLICT (transaction_hash) DO UPDATE " "SET last_status = EXCLUDED.last_status", transaction['hash'], from_address, to_address, parse_int(transaction['nonce']), str(parse_int(transaction['value'])), str( parse_int(transaction['gas']) * parse_int(transaction['gasPrice'])), sender_token_id, 'confirmed' if transaction['blockNumber'] is not None else 'unconfirmed') # if the tx was overwritten, send the tx cancel message first if overwritten_tx_hash: await self.send_notifications( [overwritten_tx_hash_to_address, from_address], overwritten_tx_token_ids, { 'hash': overwritten_tx_hash, 'to': overwritten_tx_hash_to_address, 'from': from_address, 'blockNumber': None, 'value': None, 'error': 'overwritten' }, sender_token_id) await self.send_notifications([to_address, from_address], token_ids, transaction, sender_token_id)
def get_balance(self, address, **kwargs): resp = self._fetch("/v1/balance/{}".format(address), "GET", **kwargs) return parse_int(resp["confirmed_balance"]), parse_int(resp["unconfirmed_balance"])
def test_parse_int_no_leading_zeros_string(self): self.assertEqual(parse_int("0123"), None)
def test_parse_zero_int_string(self): self.assertEqual(parse_int("0"), 0)
def test_parse_negative_int_string(self): self.assertEqual(parse_int("-12345"), -12345)
async def update_categories(request, conf, current_user): category_name = request.form.get('category', None) category_tag = request.form.get('tag', None) category_id = request.form.get('id', None) should_remove = request.form.get('remove', None) language = 'en' error = None if should_remove is not None: if category_id is None: error = "Missing category id" else: category_id = parse_int(category_id) async with conf.db.id.acquire() as con: await con.fetchval( "DELETE FROM categories WHERE category_id = $1", category_id) elif category_tag is None or category_name is None: error = "Missing name and tag" else: # force tags to be lowercase category_tag = category_tag.lower() if category_id is None: try: async with conf.db.id.acquire() as con: category_id = await con.fetchval( "INSERT INTO categories (tag) VALUES ($1) RETURNING category_id", category_tag) except UniqueViolationError: error = "Tag already exists" # check again because we only update if the category_id was supplied by the user # or the insert statement above succeeded. if category_id is not None: category_id = parse_int(category_id) async with conf.db.id.acquire() as con: await con.execute( "INSERT INTO category_names (category_id, name, language) VALUES ($1, $2, $3) " "ON CONFLICT (category_id, language) DO UPDATE SET name = EXCLUDED.name", category_id, category_name, language) get_sql = ( "SELECT * FROM categories " "JOIN category_names ON categories.category_id = category_names.category_id AND category_names.language = $1" "ORDER BY categories.tag ASC ") async with conf.db.id.acquire() as con: rows = await con.fetch(get_sql, language) return html(await env.get_template("categories.html").render_async( categories=rows, error=error, current_user=current_user, environment=conf.name, page="categories"))
def test_parse_hex_string(self): self.assertEqual(parse_int("0x12345"), 74565)
async def liveordev(request, conf, user): # get statistics async with conf.db.eth.acquire() as con: tx24h = await con.fetchrow( "SELECT COUNT(*) FROM transactions WHERE created > (now() AT TIME ZONE 'utc') - interval '24 hours'" ) tx7d = await con.fetchrow( "SELECT COUNT(*) FROM transactions WHERE created > (now() AT TIME ZONE 'utc') - interval '7 days'" ) tx1m = await con.fetchrow( "SELECT COUNT(*) FROM transactions WHERE created > (now() AT TIME ZONE 'utc') - interval '1 month'" ) txtotal = await con.fetchrow("SELECT COUNT(*) FROM transactions") last_block = await con.fetchrow("SELECT * FROM last_blocknumber") async with conf.db.id.acquire() as con: u24h = await con.fetchrow( "SELECT COUNT(*) FROM users WHERE created > (now() AT TIME ZONE 'utc') - interval '24 hours'" ) u7d = await con.fetchrow( "SELECT COUNT(*) FROM users WHERE created > (now() AT TIME ZONE 'utc') - interval '7 days'" ) u1m = await con.fetchrow( "SELECT COUNT(*) FROM users WHERE created > (now() AT TIME ZONE 'utc') - interval '1 month'" ) utotal = await con.fetchrow("SELECT COUNT(*) FROM users") users = { 'day': u24h['count'], 'week': u7d['count'], 'month': u1m['count'], 'total': utotal['count'] } txs = { 'day': tx24h['count'], 'week': tx7d['count'], 'month': tx1m['count'], 'total': txtotal['count'] } status = {} block = {'db': last_block['blocknumber']} # check service status # eth try: resp = await app.http.get('{}/v1/balance/0x{}'.format( conf.urls.eth, '0' * 40), timeout=SERVICE_CHECK_TIMEOUT) if resp.status == 200: status['eth'] = "OK" else: status['eth'] = "Error: {}".format(resp.status) except asyncio.TimeoutError: status['eth'] = "Error: timeout" # id try: resp = await app.http.get('{}/v1/user/0x{}'.format( conf.urls.id, '0' * 40), timeout=SERVICE_CHECK_TIMEOUT) if resp.status == 404: status['id'] = "OK" else: status['id'] = "Error: {}".format(resp.status) except asyncio.TimeoutError: status['id'] = "Error: timeout" # dir try: resp = await app.http.get('{}/v1/apps/'.format(conf.urls.dir), timeout=SERVICE_CHECK_TIMEOUT) if resp.status == 200: status['dir'] = "OK" else: status['dir'] = "Error: {}".format(resp.status) except asyncio.TimeoutError: status['dir'] = "Error: timeout" # rep try: resp = await app.http.get('{}/v1/timestamp'.format(conf.urls.rep), timeout=SERVICE_CHECK_TIMEOUT) if resp.status == 200: status['rep'] = "OK" else: status['rep'] = "Error: {}".format(resp.status) except asyncio.TimeoutError: status['rep'] = "Error: timeout" # node try: resp = await app.http.post( conf.urls.node, headers={'Content-Type': 'application/json'}, data=json.dumps({ "jsonrpc": "2.0", "id": random.randint(0, 1000000), "method": "eth_blockNumber", "params": [] }).encode('utf-8')) if resp.status == 200: data = await resp.json() if 'result' in data: if data['result'] is not None: status['node'] = "OK" block['node'] = parse_int(data['result']) elif 'error' in data: status['node'] = data['error'] else: status['node'] = "Error: {}".format(resp.status) except asyncio.TimeoutError: status['node'] = "Error: timeout" return html(await env.get_template("index.html").render_async( current_user=user, environment=conf.name, page="home", txs=txs, users=users, status=status, block=block))
def test_parse_float(self): self.assertEqual(parse_int(12345.45675), 12345)
async def get_apps(request, conf, current_user): page = parse_int(request.args.get('page', None)) or 1 if page < 1: page = 1 limit = 10 offset = (page - 1) * limit order_by = request.args.get('order_by', None) search_query = request.args.get('query', None) filter_by = request.args.get('filter', None) order = ('created', 'DESC') if order_by: if order_by in sortable_apps_columns: if order_by[0] == '-': order = (order_by[1:], 'ASC' if order_by[1:] in negative_apps_columns else 'DESC') else: order = (order_by, 'DESC' if order_by in negative_apps_columns else 'ASC') if search_query: # strip punctuation query = ''.join( [c for c in search_query if c not in string.punctuation]) # split words and add in partial matching flags query = '|'.join( ['{}:*'.format(word) for word in query.split(' ') if word]) args = [offset, limit, query] if order_by: query_order = "ORDER BY {} {}".format(*order) else: # default order by rank query_order = "ORDER BY TS_RANK_CD(t1.tsv, TO_TSQUERY($3)) DESC, name, username" sql = ("SELECT * FROM " "(SELECT * FROM users, TO_TSQUERY($3) AS q " "WHERE (tsv @@ q) AND is_app = true) AS t1 " "{} " "OFFSET $1 LIMIT $2".format(query_order)) count_args = [query] count_sql = ("SELECT COUNT(*) FROM users, TO_TSQUERY($1) AS q " "WHERE (tsv @@ q) AND is_app = true") async with conf.db.id.acquire() as con: rows = await con.fetch(sql, *args) count = await con.fetchrow(count_sql, *count_args) else: async with conf.db.id.acquire() as con: rows = await con.fetch( "SELECT * FROM users WHERE is_app = true ORDER BY {} {} NULLS LAST OFFSET $1 LIMIT $2" .format(*order), offset, limit) count = await con.fetchrow( "SELECT COUNT(*) FROM users WHERE is_app = true") apps = [] for row in rows: app = fix_avatar_for_user(conf.urls.id, dict(row)) apps.append(app) total_pages = (count['count'] // limit) + (0 if count['count'] % limit == 0 else 1) def get_qargs(page=page, order_by=order_by, query=search_query, filter=filter_by, as_list=False, as_dict=False): qargs = {'page': page} if order_by: if order_by[0] == '+': order_by = order_by[1:] elif order_by[0] != '-': # toggle sort order print(order, order_by) if order[0] == order_by and order[1] == ( 'ASC' if order_by in negative_user_columns else 'DESC'): order_by = '-{}'.format(order_by) qargs['order_by'] = order_by if query: qargs['query'] = query if filter: qargs['filter'] = filter if as_dict: return qargs if as_list: return qargs.items() return urlencode(qargs) return html(await env.get_template("apps.html").render_async( apps=apps, current_user=current_user, environment=conf.name, page="apps", total=count['count'], total_pages=total_pages, current_page=page, get_qargs=get_qargs))
def test_parse_decimal(self): self.assertEqual(parse_int(Decimal("12345.6787")), 12345)