<commit_before>
'''miscellaneous utilities.'''

import binascii
from .provider import *
from .exceptions import P2THImportFailed
from .exceptions import (InvalidDeckSpawn, InvalidDeckMetainfo,
                         InvalidDeckIssueMode, InvalidDeckVersion)
from .exceptions import (InvalidCardTransferP2TH, CardVersionMistmatch,
                         CardNumberOfDecimalsMismatch)
from .constants import param_query, params
from typing import Iterator
from .paproto_pb2 import DeckSpawn, CardTransfer

def load_p2th_privkey_into_local_node(provider: RpcNode, prod: bool=True) -> None:
    '''Load PeerAssets P2TH privkey into the local node.'''

    assert isinstance(provider, RpcNode), {"error": "Import only works with local node."}
    error = {"error": "Loading P2TH privkey failed."}
    pa_params = param_query(provider.network)

    if prod:
        provider.importprivkey(pa_params.P2TH_wif, "PAPROD")
        #  now verify if ismine == True
        if not provider.validateaddress(pa_params.P2TH_addr)['ismine']:
            raise P2THImportFailed(error)
    else:
        provider.importprivkey(pa_params.test_P2TH_wif, "PATEST")
        if not provider.validateaddress(pa_params.test_P2TH_addr)['ismine']:
            raise P2THImportFailed(error)

def find_tx_sender(provider, raw_tx: dict) -> str:
    '''find transaction sender, vin[0] is used in this case.'''

    vin = raw_tx["vin"][0]
    txid = vin["txid"]
    index = vin["vout"]
    return provider.getrawtransaction(txid, 1)["vout"][index]["scriptPubKey"]["addresses"][0]

def find_deck_spawns(provider, prod=True):
    '''find deck spawn transactions via provider,
    it requires that Deck spawn P2TH were imported in local node or
    that remote API knows about P2TH address.'''

    pa_params = param_query(provider.network)

    if isinstance(provider, RpcNode):

        if prod:
            decks = (i["txid"] for i in provider.listtransactions("PAPROD"))
        else:
            decks = (i["txid"] for i in provider.listtransactions("PATEST"))

    if isinstance(provider, Mintr):

        if prod:
            decks = (i["txid"] for i in provider.listtransactions(pa_params.P2TH_addr))
        else:
            raise NotImplementedError

    if isinstance(provider, Holy) or isinstance(provider, Cryptoid):

        if prod:
            decks = (i for i in provider.listtransactions(pa_params.P2TH_addr))
        else:
            decks = (i for i in provider.listtransactions(pa_params.test_P2TH_addr))

    return decks

def tx_serialization_order(provider, blockhash: str, txid: str) -> int:
    '''find index of this tx in the blockid'''

    return provider.getblock(blockhash)["tx"].index(txid)

def read_tx_opreturn(raw_tx: dict) -> bytes:
    '''Decode OP_RETURN message from raw_tx'''

    vout = raw_tx['vout'][1]  # PA protocol requires that OP_RETURN is vout[1]

    asm = vout['scriptPubKey']['asm']
    n = asm.find('OP_RETURN')
    if n == -1:
        return False #{'error': 'OP_RETURN not found'}
    else:
        # add 10 because 'OP_RETURN ' is 10 characters
        n += 10
        data = asm[n:]
        n = data.find(' ')
        #make sure that we don't include trailing opcodes
        if n == -1:
            return binascii.unhexlify(data)
        else:
            return binascii.unhexlify(data[:n])

def deck_issue_mode(proto: DeckSpawn) -> Iterator[str]:
    '''interpret issue mode bitfeg'''

    for mode in proto.MODE.keys():
        if proto.issue_mode & proto.MODE.Value(mode):
            yield mode

def issue_mode_to_enum(deck: DeckSpawn, issue_mode: list) -> int:
    '''encode issue mode(s) as bitfeg'''

    # case where there are multiple issue modes specified
    if isinstance(issue_mode, list) and len(issue_mode) > 1:
        r = 0
        for mode in issue_mode:
            r += deck.MODE.Value(mode)
        return r

    elif isinstance(issue_mode, str):  # if single issue mode
        return deck.MODE.Value(issue_mode)

    else:
        raise InvalidDeckIssueMode({'error': 'issue_mode given in wrong format.'})

def parse_deckspawn_metainfo(protobuf: bytes, version: int) -> dict:
    '''Decode deck_spawn tx op_return protobuf message and validate it,
       Raise error if deck_spawn metainfo incomplete or version mistmatch.'''

    deck = DeckSpawn()
    deck.ParseFromString(protobuf)

    error = {"error": "Deck ({deck}) metainfo incomplete, deck must have a name.".format(deck=deck.name)}

    if deck.name == "":
        raise InvalidDeckMetainfo(error)

    if deck.version != version:
        raise InvalidDeckVersion({"error", "Deck version mismatch."})

    return {
        "version": deck.version,
        "name": deck.name,
        "issue_mode": list(deck_issue_mode(deck)),
        "number_of_decimals": deck.number_of_decimals,
        "asset_specific_data": deck.asset_specific_data
    }

def validate_deckspawn_p2th(provider, rawtx, prod=True):
    '''validate if deck spawn pays to p2th in vout[0] and if it correct P2TH address'''

    error = {"Error": "This deck ({deck}) is not properly tagged.".format(deck=rawtx['txid'])}
    pa_params = param_query(provider.network)

    try:
        vout = rawtx["vout"][0]["scriptPubKey"].get("addresses")[0]
    except TypeError:
        '''TypeError: 'NoneType' object is not subscriptable error on some of the deck spawns.'''
        raise InvalidDeckSpawn(error)

    if prod:
        if not vout == pa_params.P2TH_addr:
            raise InvalidDeckSpawn(error)
        return True

    else:
        if not vout == pa_params.test_P2TH_addr:
            raise InvalidDeckSpawn(error)
        return True

def load_deck_p2th_into_local_node(provider, deck) -> None:
    '''
    load deck p2th into local node,
    this allows building of proof-of-timeline for this deck
    '''

    assert isinstance(provider, RpcNode), {"error": "You can load privkeys only into local node."}
    error = {"error": "Deck P2TH import went wrong."}

    provider.importprivkey(deck.p2th_wif, deck.asset_id)
    check_addr = provider.validateaddress(deck.p2th_address)
    assert check_addr["isvalid"] and check_addr["ismine"], error

def validate_card_transfer_p2th(deck, raw_tx: dict) -> None:
    '''validate if card_transfer transaction pays to deck p2th in vout[0]'''

    error = {"error": "Card transfer is not properly tagged."}

    if not raw_tx["vout"][0]["scriptPubKey"].get("addresses")[0] == deck.p2th_address:
        raise InvalidCardTransferP2TH(error)

def parse_card_transfer_metainfo(protobuf: bytes, deck_version: int) -> dict:
    '''decode card_spawn protobuf message and validate it against deck.version'''

    card = CardTransfer()
    card.ParseFromString(protobuf)

    if not card.version == deck_version:
        raise CardVersionMistmatch({'error': 'card version does not match deck version.'})

    return {
        "version": card.version,
        "number_of_decimals": card.number_of_decimals,
        "amount": card.amount,
        "asset_specific_data": card.asset_specific_data
    }

def postprocess_card(card_metainfo: CardTransfer, raw_tx: dict, sender: str,
                     vout: list, blockseq: int, blocknum: int, deck) -> list:
    '''Postprocessing of all the relevant card transfer information and
    the creation of CardTransfer object.

    : card_metainfo: card_transfer protobuf
    : raw_tx: raw transaction
    : sender: tx sender
    : vout: tx vout
    : blockseq: tx block sequence number
    : blocknum: block number
    : deck: deck object this card transfer belongs to'''

    nderror = {"error": "Number of decimals does not match."}

    _card = {}
    _card["version"] = card_metainfo["version"]
    _card["number_of_decimals"] = card_metainfo["number_of_decimals"]
    # check if card number of decimals matches the deck atribute
    if not _card["number_of_decimals"] == deck.number_of_decimals:
        raise CardNumberOfDecimalsMismatch(nderror)

    _card["deck"] = deck
    _card["txid"] = raw_tx["txid"]
    try:
        _card["blockhash"] = raw_tx["blockhash"]
    except KeyError:
        _card["blockhash"] = 0
    if blockseq:
        _card["blockseq"] = blockseq
    if blocknum:
        _card["blocknum"] = blocknum
    _card["timestamp"] = raw_tx["time"]
    _card["sender"] = sender
    _card["asset_specific_data"] = card_metainfo["asset_specific_data"]

    if len(card_metainfo["amount"]) > 1:  # if card states multiple outputs:
        cards = []
        for am, v in zip(card_metainfo["amount"], vout[2:]):
            c = _card.copy()
            c["amount"] = [am]
            c["receiver"] = v["scriptPubKey"]["addresses"]
            c["cardseq"] = vout[2:].index(v)

            cards.append(c)
        return cards
    else:
        _card["receiver"] = vout[2]["scriptPubKey"]["addresses"]
        _card["amount"] = card_metainfo["amount"]
        _card["cardseq"] = 0

    return (_card, )

def amount_to_exponent(amount: float, number_of_decimals: int) -> int:
    '''encode amount integer as exponent'''

    return int(amount * 10**number_of_decimals)

def exponent_to_amount(exponent: int, number_of_decimals: int) -> float:
    '''exponent to integer to be written on the chain'''

    return exponent / 10**number_of_decimals
<commit_msg>parse fee from deck spawn.<commit_after>
'''miscellaneous utilities.'''

import binascii
from .provider import *
from .exceptions import P2THImportFailed
from .exceptions import (InvalidDeckSpawn, InvalidDeckMetainfo,
                         InvalidDeckIssueMode, InvalidDeckVersion)
from .exceptions import (InvalidCardTransferP2TH, CardVersionMistmatch,
                         CardNumberOfDecimalsMismatch)
from .constants import param_query, params
from typing import Iterator
from .paproto_pb2 import DeckSpawn, CardTransfer

def load_p2th_privkey_into_local_node(provider: RpcNode, prod: bool=True) -> None:
    '''Load PeerAssets P2TH privkey into the local node.'''

    assert isinstance(provider, RpcNode), {"error": "Import only works with local node."}
    error = {"error": "Loading P2TH privkey failed."}
    pa_params = param_query(provider.network)

    if prod:
        provider.importprivkey(pa_params.P2TH_wif, "PAPROD")
        #  now verify if ismine == True
        if not provider.validateaddress(pa_params.P2TH_addr)['ismine']:
            raise P2THImportFailed(error)
    else:
        provider.importprivkey(pa_params.test_P2TH_wif, "PATEST")
        if not provider.validateaddress(pa_params.test_P2TH_addr)['ismine']:
            raise P2THImportFailed(error)

def find_tx_sender(provider, raw_tx: dict) -> str:
    '''find transaction sender, vin[0] is used in this case.'''

    vin = raw_tx["vin"][0]
    txid = vin["txid"]
    index = vin["vout"]
    return provider.getrawtransaction(txid, 1)["vout"][index]["scriptPubKey"]["addresses"][0]

def find_deck_spawns(provider, prod=True):
    '''find deck spawn transactions via provider,
    it requires that Deck spawn P2TH were imported in local node or
    that remote API knows about P2TH address.'''

    pa_params = param_query(provider.network)

    if isinstance(provider, RpcNode):

        if prod:
            decks = (i["txid"] for i in provider.listtransactions("PAPROD"))
        else:
            decks = (i["txid"] for i in provider.listtransactions("PATEST"))

    if isinstance(provider, Mintr):

        if prod:
            decks = (i["txid"] for i in provider.listtransactions(pa_params.P2TH_addr))
        else:
            raise NotImplementedError

    if isinstance(provider, Holy) or isinstance(provider, Cryptoid):

        if prod:
            decks = (i for i in provider.listtransactions(pa_params.P2TH_addr))
        else:
            decks = (i for i in provider.listtransactions(pa_params.test_P2TH_addr))

    return decks

def tx_serialization_order(provider, blockhash: str, txid: str) -> int:
    '''find index of this tx in the blockid'''

    return provider.getblock(blockhash)["tx"].index(txid)

def read_tx_opreturn(raw_tx: dict) -> bytes:
    '''Decode OP_RETURN message from raw_tx'''

    vout = raw_tx['vout'][1]  # PA protocol requires that OP_RETURN is vout[1]

    asm = vout['scriptPubKey']['asm']
    n = asm.find('OP_RETURN')
    if n == -1:
        return False #{'error': 'OP_RETURN not found'}
    else:
        # add 10 because 'OP_RETURN ' is 10 characters
        n += 10
        data = asm[n:]
        n = data.find(' ')
        #make sure that we don't include trailing opcodes
        if n == -1:
            return binascii.unhexlify(data)
        else:
            return binascii.unhexlify(data[:n])

def deck_issue_mode(proto: DeckSpawn) -> Iterator[str]:
    '''interpret issue mode bitfeg'''

    for mode in proto.MODE.keys():
        if proto.issue_mode & proto.MODE.Value(mode):
            yield mode

def issue_mode_to_enum(deck: DeckSpawn, issue_mode: list) -> int:
    '''encode issue mode(s) as bitfeg'''

    # case where there are multiple issue modes specified
    if isinstance(issue_mode, list) and len(issue_mode) > 1:
        r = 0
        for mode in issue_mode:
            r += deck.MODE.Value(mode)
        return r

    elif isinstance(issue_mode, str):  # if single issue mode
        return deck.MODE.Value(issue_mode)

    else:
        raise InvalidDeckIssueMode({'error': 'issue_mode given in wrong format.'})

def parse_deckspawn_metainfo(protobuf: bytes, version: int) -> dict:
    '''Decode deck_spawn tx op_return protobuf message and validate it,
       Raise error if deck_spawn metainfo incomplete or version mistmatch.'''

    deck = DeckSpawn()
    deck.ParseFromString(protobuf)

    error = {"error": "Deck ({deck}) metainfo incomplete, deck must have a name.".format(deck=deck.name)}

    if deck.name == "":
        raise InvalidDeckMetainfo(error)

    if deck.version != version:
        raise InvalidDeckVersion({"error", "Deck version mismatch."})

    return {
        "version": deck.version,
        "name": deck.name,
        "issue_mode": list(deck_issue_mode(deck)),
        "number_of_decimals": deck.number_of_decimals,
        "asset_specific_data": deck.asset_specific_data,
        "fee": exponent_to_amount(deck.fee, deck.number_of_decimals)  # deck.fee is encoded as exponent
    }

def validate_deckspawn_p2th(provider, rawtx, prod=True):
    '''validate if deck spawn pays to p2th in vout[0] and if it correct P2TH address'''

    error = {"Error": "This deck ({deck}) is not properly tagged.".format(deck=rawtx['txid'])}
    pa_params = param_query(provider.network)

    try:
        vout = rawtx["vout"][0]["scriptPubKey"].get("addresses")[0]
    except TypeError:
        '''TypeError: 'NoneType' object is not subscriptable error on some of the deck spawns.'''
        raise InvalidDeckSpawn(error)

    if prod:
        if not vout == pa_params.P2TH_addr:
            raise InvalidDeckSpawn(error)
        return True

    else:
        if not vout == pa_params.test_P2TH_addr:
            raise InvalidDeckSpawn(error)
        return True

def load_deck_p2th_into_local_node(provider, deck) -> None:
    '''
    load deck p2th into local node,
    this allows building of proof-of-timeline for this deck
    '''

    assert isinstance(provider, RpcNode), {"error": "You can load privkeys only into local node."}
    error = {"error": "Deck P2TH import went wrong."}

    provider.importprivkey(deck.p2th_wif, deck.asset_id)
    check_addr = provider.validateaddress(deck.p2th_address)
    assert check_addr["isvalid"] and check_addr["ismine"], error

def validate_card_transfer_p2th(deck, raw_tx: dict) -> None:
    '''validate if card_transfer transaction pays to deck p2th in vout[0]'''

    error = {"error": "Card transfer is not properly tagged."}

    if not raw_tx["vout"][0]["scriptPubKey"].get("addresses")[0] == deck.p2th_address:
        raise InvalidCardTransferP2TH(error)

def parse_card_transfer_metainfo(protobuf: bytes, deck_version: int) -> dict:
    '''decode card_spawn protobuf message and validate it against deck.version'''

    card = CardTransfer()
    card.ParseFromString(protobuf)

    if not card.version == deck_version:
        raise CardVersionMistmatch({'error': 'card version does not match deck version.'})

    return {
        "version": card.version,
        "number_of_decimals": card.number_of_decimals,
        "amount": card.amount,
        "asset_specific_data": card.asset_specific_data
    }

def postprocess_card(card_metainfo: CardTransfer, raw_tx: dict, sender: str,
                     vout: list, blockseq: int, blocknum: int, deck) -> list:
    '''Postprocessing of all the relevant card transfer information and
    the creation of CardTransfer object.

    : card_metainfo: card_transfer protobuf
    : raw_tx: raw transaction
    : sender: tx sender
    : vout: tx vout
    : blockseq: tx block sequence number
    : blocknum: block number
    : deck: deck object this card transfer belongs to'''

    nderror = {"error": "Number of decimals does not match."}

    _card = {}
    _card["version"] = card_metainfo["version"]
    _card["number_of_decimals"] = card_metainfo["number_of_decimals"]
    # check if card number of decimals matches the deck atribute
    if not _card["number_of_decimals"] == deck.number_of_decimals:
        raise CardNumberOfDecimalsMismatch(nderror)

    _card["deck"] = deck
    _card["txid"] = raw_tx["txid"]
    try:
        _card["blockhash"] = raw_tx["blockhash"]
    except KeyError:
        _card["blockhash"] = 0
    if blockseq:
        _card["blockseq"] = blockseq
    if blocknum:
        _card["blocknum"] = blocknum
    _card["timestamp"] = raw_tx["time"]
    _card["sender"] = sender
    _card["asset_specific_data"] = card_metainfo["asset_specific_data"]

    if len(card_metainfo["amount"]) > 1:  # if card states multiple outputs:
        cards = []
        for am, v in zip(card_metainfo["amount"], vout[2:]):
            c = _card.copy()
            c["amount"] = [am]
            c["receiver"] = v["scriptPubKey"]["addresses"]
            c["cardseq"] = vout[2:].index(v)

            cards.append(c)
        return cards
    else:
        _card["receiver"] = vout[2]["scriptPubKey"]["addresses"]
        _card["amount"] = card_metainfo["amount"]
        _card["cardseq"] = 0

    return (_card, )

def amount_to_exponent(amount: float, number_of_decimals: int) -> int:
    '''encode amount integer as exponent'''

    return int(amount * 10**number_of_decimals)

def exponent_to_amount(exponent: int, number_of_decimals: int) -> float:
    '''exponent to integer to be written on the chain'''

    return exponent / 10**number_of_decimals
<|endoftext|>


<commit_before>from primative import *
import time
import copy
import itertools
from collections import deque

class SolutionGenerator (object):
    direction_reverse = {}
    direction_delta = {}

    def __init__ (self, boardsection, robots, directions):
        self.board = list(itertools.chain.from_iterable(boardsection._board))
        self.robot_objects = copy.deepcopy (robots)
        self.directions = directions
        self.direction_reverse = {
                0b00000001: 0b00000010,
                0b00000010: 0b00000001,
                0b00000100: 0b00001000,
                0b00001000: 0b00000100,
            }
        self.direction_delta = {
                0b00000001: -16,
                0b00000010: +16,
                0b00000100: +1,
                0b00001000: -1,
            }

    def node_to_int (self, node):
        first, second, third = node[0][0], node[1][0], node[2][0]
        if first > second:
            first, second = second, first
        if second > third:
            second, third = third, second
        if first > second:
            first, second = second, first
        return node[3][0] | first << 8 | second << 16 | third << 24

    def cell_move (self, index, direction, node):
        while True:
            if self.board[index] & direction == direction:
                break
            advanced_index = index + self.direction_delta[direction]
            for r in node:
                if advanced_index == r[0]:
                    break
            else:
                index = advanced_index
                continue
            break
        return index

    def moves_from_robots (self, node, goal_index):
        moves = deque()
        for i, r in enumerate(self.robot_objects):
            for d in self.directions:
                if node[i][1] not in (d.value, self.direction_reverse[d.value]):
                    #ricochet rule
                    new_cell = node[i][0]
                    advanced_index = 0
                    while True:
                        if self.board[new_cell] & d.value == d.value:
                            break
                        advanced_index = new_cell + self.direction_delta[d.value]
                        for r in node:
                            if advanced_index == r[0]:
                                break
                        else:
                            new_cell = advanced_index
                            continue
                        break
                    if (node[i][0] != new_cell and not (
                            i == 3 and
                            node[i][1] == 0 and
                            new_cell == goal_index
                        )
                    ):
                        updated_robots = copy.copy (node)
                        updated_robots[i] = (new_cell, d.value)
                        moves.append (updated_robots)
        return moves

    def generate (self, robots, goal, directions=None, verbose=False):
        #robots = dictionary key robot value point
        assert len(robots) == 4
        #goal = list of goals
        assert len(goal.robots) == 1#multi goal not supported
        #directions = list of last direction values or 0 for none
        time_start = time.clock()
        if directions is not None:
            assert len(directions) == len(robots)
        else:
            directions = [0] * len(robots)
        #reorganize goal object last
        for i, r in enumerate (self.robot_objects):
            if r.value == goal.robots[0].value:
                self.robot_objects.append (self.robot_objects.pop(i))
                break
        start_position = list(
                [   (robots[r].y * 16 + robots[r].x, directions[ii])
                    for i, ro in enumerate(self.robot_objects)
                        for ii, r in enumerate(robots)
                            if ro.value == r.value]
            )
        #reorder our robots - put the goal robot last @ [3]
        goal_index = goal.point.y * 16 + goal.point.x
        for d in self.directions:
            new_cell = self.cell_move (goal_index, d.value, [])
            if new_cell != goal_index:
                if d.value in (4,8):#EorW
                    minx = new_cell if new_cell < goal_index else goal_index
                    maxx = new_cell if new_cell > goal_index else goal_index
                elif d.value in (1,2):#NorS
                    miny = new_cell if new_cell < goal_index else goal_index
                    maxy = new_cell if new_cell > goal_index else goal_index
        minymod = miny % 16
        positions_seen = {}
        #directary of seen positions
        path_length = 1
        total_seen_count = 0
        seen_count = 0
        skipped_count = 0
        queue = deque()
        queue.append ([start_position])
        while queue:
            path = queue.popleft()
            node = path[-1]
            if len(path) != path_length:
                if verbose:
                    print (
                            "{0:02d}".format (path_length-1) + " - " +
                            "{0:05d}".format (seen_count) + "\t sk: " +
                            "{0:05d}".format (skipped_count) + "\t@ " +
                            str(time.clock() - time_start) + "s"
                        )
                path_length = len(path)
                total_seen_count += seen_count
                seen_count = 0
            seen_count += 1
            if (
                (goal_index == node[3][0]) or
                (
                    (
                        (node[3][1] in (1,2) and maxx >= node[3][0] >= minx) or
                        (node[3][1] in (3,4) and maxy >= node[3][0] >= miny and node[3][0] % 16 == minymod)
                    )
                )
            ):
                total_seen_count += seen_count
                if verbose:
                    print ("\tanswer found in " + str(time.clock() - time_start) + "s")
                    print (
                            "\tL" + str(path_length-1) +
                            " - total seen:" + str(total_seen_count) +
                            "; skipped: " + str(skipped_count) +
                            "; cache: " + str(len(positions_seen))
                        )
                return path
            for adjacent in self.moves_from_robots (node, goal_index):
                first = adjacent[0][0]; second = adjacent[1][0]; third = adjacent[2][0]
                if first > second:
                    first, second = second, first
                if second > third:
                    second, third = third, second
                if first > second:
                    first, second = second, first
                key = adjacent[3][0] | first << 8 | second << 16 | third << 24
                if positions_seen.get (key) is None:
                    positions_seen[key] = True
                    new_path = deque(list (path))
                    new_path.append(adjacent)
                    queue.append(new_path)
                else:
                    skipped_count += 1<commit_msg>summary display when no solution found<commit_after>from primative import *
import time
import copy
import itertools
from collections import deque

class SolutionGenerator (object):
    direction_reverse = {}
    direction_delta = {}

    def __init__ (self, boardsection, robots, directions):
        self.board = list(itertools.chain.from_iterable(boardsection._board))
        self.robot_objects = copy.deepcopy (robots)
        self.directions = directions
        self.direction_reverse = {
                0b00000001: 0b00000010,
                0b00000010: 0b00000001,
                0b00000100: 0b00001000,
                0b00001000: 0b00000100,
            }
        self.direction_delta = {
                0b00000001: -16,
                0b00000010: +16,
                0b00000100: +1,
                0b00001000: -1,
            }

    def node_to_int (self, node):
        first, second, third = node[0][0], node[1][0], node[2][0]
        if first > second:
            first, second = second, first
        if second > third:
            second, third = third, second
        if first > second:
            first, second = second, first
        return node[3][0] | first << 8 | second << 16 | third << 24

    def cell_move (self, index, direction, node):
        while True:
            if self.board[index] & direction == direction:
                break
            advanced_index = index + self.direction_delta[direction]
            for r in node:
                if advanced_index == r[0]:
                    break
            else:
                index = advanced_index
                continue
            break
        return index

    def moves_from_robots (self, node, goal_index):
        moves = deque()
        for i, r in enumerate(self.robot_objects):
            for d in self.directions:
                if node[i][1] not in (d.value, self.direction_reverse[d.value]):
                    #ricochet rule
                    new_cell = node[i][0]
                    advanced_index = 0
                    while True:
                        if self.board[new_cell] & d.value == d.value:
                            break
                        advanced_index = new_cell + self.direction_delta[d.value]
                        for r in node:
                            if advanced_index == r[0]:
                                break
                        else:
                            new_cell = advanced_index
                            continue
                        break
                    if (node[i][0] != new_cell and not (
                            i == 3 and
                            node[i][1] == 0 and
                            new_cell == goal_index
                        )
                    ):
                        updated_robots = copy.copy (node)
                        updated_robots[i] = (new_cell, d.value)
                        moves.append (updated_robots)
        return moves

    def generate (self, robots, goal, directions=None, verbose=False):
        #robots = dictionary key robot value point
        assert len(robots) == 4
        #goal = list of goals
        assert len(goal.robots) == 1#multi goal not supported
        #directions = list of last direction values or 0 for none
        time_start = time.clock()
        if directions is not None:
            assert len(directions) == len(robots)
        else:
            directions = [0] * len(robots)
        #reorganize goal object last
        for i, r in enumerate (self.robot_objects):
            if r.value == goal.robots[0].value:
                self.robot_objects.append (self.robot_objects.pop(i))
                break
        start_position = list(
                [   (robots[r].y * 16 + robots[r].x, directions[ii])
                    for i, ro in enumerate(self.robot_objects)
                        for ii, r in enumerate(robots)
                            if ro.value == r.value]
            )
        #reorder our robots - put the goal robot last @ [3]
        goal_index = goal.point.y * 16 + goal.point.x
        for d in self.directions:
            new_cell = self.cell_move (goal_index, d.value, [])
            if new_cell != goal_index:
                if d.value in (4,8):#EorW
                    minx = new_cell if new_cell < goal_index else goal_index
                    maxx = new_cell if new_cell > goal_index else goal_index
                elif d.value in (1,2):#NorS
                    miny = new_cell if new_cell < goal_index else goal_index
                    maxy = new_cell if new_cell > goal_index else goal_index
        minymod = miny % 16
        positions_seen = {}
        #directary of seen positions
        path_length = 1
        total_seen_count = 0
        seen_count = 0
        skipped_count = 0
        queue = deque()
        queue.append ([start_position])
        while queue:
            path = queue.popleft()
            node = path[-1]
            if len(path) != path_length:
                if verbose:
                    print (
                            "{0:02d}".format (path_length-1) + " - " +
                            "{0:05d}".format (seen_count) + "\t sk: " +
                            "{0:05d}".format (skipped_count) + "\t@ " +
                            str(time.clock() - time_start) + "s"
                        )
                path_length = len(path)
                total_seen_count += seen_count
                seen_count = 0
            seen_count += 1
            if (
                (goal_index == node[3][0]) or
                (
                    (
                        (node[3][1] in (1,2) and maxx >= node[3][0] >= minx) or
                        (node[3][1] in (3,4) and maxy >= node[3][0] >= miny and node[3][0] % 16 == minymod)
                    )
                )
            ):
                total_seen_count += seen_count
                if verbose:
                    print ("\tanswer found in " + str(time.clock() - time_start) + "s")
                    print (
                            "\tL" + str(path_length-1) +
                            " - total seen:" + str(total_seen_count) +
                            "; skipped: " + str(skipped_count) +
                            "; cache: " + str(len(positions_seen))
                        )
                return path
            for adjacent in self.moves_from_robots (node, goal_index):
                first = adjacent[0][0]; second = adjacent[1][0]; third = adjacent[2][0]
                if first > second:
                    first, second = second, first
                if second > third:
                    second, third = third, second
                if first > second:
                    first, second = second, first
                key = adjacent[3][0] | first << 8 | second << 16 | third << 24
                if positions_seen.get (key) is None:
                    positions_seen[key] = True
                    new_path = deque(list (path))
                    new_path.append(adjacent)
                    queue.append(new_path)
                else:
                    skipped_count += 1
        print ("\tanswer NOT found in " + str(time.clock() - time_start) + "s")
        print (
                "\tL" + str(path_length-1) +
                " - total seen:" + str(total_seen_count) +
                "; skipped: " + str(skipped_count) +
                "; cache: " + str(len(positions_seen))
            ) <|endoftext|>


<commit_before># Copyright 2016 The Novo Nordisk Foundation Center for Biosustainability, DTU.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from functools import partial

import numpy

from cameo.core.manipulation import swap_cofactors, increase_flux, decrease_flux, reverse_flux

try:
    from gnomic import Accession, Feature, Del, Mutation, Sub, Ins
    _gnomic_available_ = True
except (ImportError, SyntaxError):  # SyntaxError from py2 incompatible syntax
    _gnomic_available_ = False

from cameo import ui
from cameo.exceptions import IncompatibleTargets

__all__ = ["GeneModulationTarget", "GeneKnockoutTarget", "ReactionCofactorSwapTarget", "ReactionKnockinTarget",
           "ReactionKnockoutTarget", "ReactionModulationTarget"]

# TODO: This should be ABC.
class Target(object):
    """
    A Target is element of a COBRA model that will change to achieve a given phenotype.
    They are identified by StrainDesignMethod.run when identifying which manipulations
    are necessary to improve some fitness function.

    Attributes
    ----------
    id: str
        The identifier of the target. The id must be present in the COBRA model.

    """
    def __init__(self, id):
        self.id = id

    def apply(self, model, time_machine=None):
        """
        Applies the modification on the target, depending on the target type.

        See Also
        --------
        Subclass implementations

        """
        raise NotImplementedError

    def __eq__(self, other):
        if isinstance(other, Target):
            return other.id == self.id
        else:
            return False

    def __gt__(self, other):
        return self.id > other.id

    def to_gnomic(self):
        """
        If gnomic is available, return a Gnomic representation of the Target.

        """
        if _gnomic_available_:
            return Accession(identifier=self.id)
        else:
            raise SystemError("Gnomic is only compatible with python >= 3 (%i.%i)" %
                              (sys.version_info.major, sys.version_info.minor))

    def __str__(self):
        return self.id

class FluxModulationTarget(Target):
    """
    A Target that changes the flux constraints (knockouts, over-expression or down-regulation).

    See Also
    --------
    ReactionModulationTarget, ReactionKnockoutTarget, GeneModulationTarget and GeneKnockoutTarget for implementation.

    """
    __gnomic_feature_type__ = 'flux'

    def __init__(self, id, value, reference_value):
        super(FluxModulationTarget, self).__init__(id)
        self._value = value
        self._reference_value = reference_value

    def get_model_target(self, model):
        raise NotImplementedError

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)

        if self._value == 0:
            target.knock_out(time_machine=time_machine)
        elif abs(self._value) > abs(self._reference_value):
            increase_flux(target, self._reference_value, self._value, time_machine=time_machine)
        elif abs(self._value) < abs(self._reference_value):
            decrease_flux(target, self._reference_value, self._value, time_machine=time_machine)

    def __eq__(self, other):
        if isinstance(other, FluxModulationTarget):
            return self.id == other.id and self._value == other._value
        else:
            return False

    @property
    def fold_change(self):
        """
        (B - A)/A

        Return
        ------
            float
        """
        try:
            return (self._reference_value - self._value) / self._value
        except ZeroDivisionError:
            return numpy.inf

    def __str__(self):
        if self._value == 0:
            return ui.delta() + self.id
        elif self._value > self._reference_value:
            return ui.upreg(self.fold_change) + self.id
        elif self._value < self._reference_value:
            return ui.downreg(self.fold_change) + self.id

    def _repr_html_(self):
        if self._value == 0:
            return "Δ%s" % self.id
        elif self._value > self._reference_value:
            return "↑(%.3f)%s" % (self.fold_change, self.id)
        elif self._value < self._reference_value:
            return "↓(%.3f)%s" % (self.fold_change, self.id)

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        feature = Feature(accession=accession, type=self.__gnomic_feature_type__)
        if self._value == 0:
            return Del(feature)
        elif abs(self._value) > abs(self._reference_value):
            over_expression = Feature(accession=accession, type=self.__gnomic_feature_type__,
                                      variant="over-expression(%.3f)" % self.fold_change)
            return Mutation(feature, over_expression)
        elif abs(self._value) < abs(self._reference_value):
            under_expression = Feature(accession=accession, type=self.__gnomic_feature_type__,
                                       variant="down-regulation(%.3f)" % self.fold_change)
            return Mutation(feature, under_expression)

class ReactionCofactorSwapTarget(Target):
    """
    Swap cofactors of a given reaction.
    """
    def __init__(self, id, swap_pairs):
        super(ReactionCofactorSwapTarget, self).__init__(id)
        self.swap_pairs = swap_pairs

    def apply(self, model, time_machine=None):
        reaction = model.reactions.get_by_id(self.id)
        swap_cofactors(reaction, model, self.swap_pairs, time_machine=time_machine)

    @property
    def swap_str(self):
        return "+".join(str(s) for s in self.swap_pairs[0]) + "<->" + "+".join(str(s) for s in self.swap_pairs[1])

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        new_accession = Accession(self.id + self.swap_str)
        original_feature = Feature(accession=accession, type='reaction')
        new_feature = Feature(accession=new_accession, type='reaction')
        return Sub(original_feature, new_feature)

    def __str__(self):
        return "<ReactionCofactorSwap %s swap=%s>" % (self.id, self.swap_str)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            if isinstance(other, ReactionModulationTarget):
                return False
            if isinstance(other, ReactionKnockinTarget):
                return True
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionCofactorSwapTarget):
            return self.id == other.id and self.swap_pairs == other.swap_pairs
        else:
            return False

    def _repr_html_(self):
        return str(self).replace("<->", "⇄")

class KnockinTarget(Target):
    def __init__(self, id, value):
        super(KnockinTarget, self).__init__(id)
        self._value = value

    def to_gnomic(self):
        raise NotImplementedError

class ReactionKnockinTarget(KnockinTarget):
    def __init__(self, id, value):
        super(ReactionKnockinTarget, self).__init__(id, value)

    def apply(self, model, time_machine=None):
        if time_machine is None:
            model.add_reaction(self._value)
        else:
            time_machine(do=partial(model.add_reaction, self._value),
                         undo=partial(model.remove_reactions, self._value, delete=False, remove_orphans=True))

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        feature = Feature(accession=accession, type='reaction')
        return Ins(feature)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionModulationTarget):
                if other._value == 0:
                    raise IncompatibleTargets(self, other)
                else:
                    return False
            elif isinstance(other, ReactionKnockinTarget):
                return False
            elif isinstance(other, ReactionCofactorSwapTarget):
                return False
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionKnockinTarget):
            return self.id == other.id
        else:
            return False

    def __str__(self):
        return "<ReactionKnockin %s>" % self.id

    def _repr_html_(self):
        return "::%s" % self.id

class GeneModulationTarget(FluxModulationTarget):
    __gnomic_feature_type__ = "gene"

    def __init__(self, id, value, reference_value):
        super(GeneModulationTarget, self).__init__(id, value, reference_value)

    def get_model_target(self, model):
        return model.genes.get_by_id(self.id)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, GeneKnockoutTarget):
                return False
            elif isinstance(other, GeneModulationTarget) and not isinstance(other, GeneKnockoutTarget):
                return self.fold_change > other.fold_change
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, GeneModulationTarget):
            return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value
        else:
            return False

class GeneKnockoutTarget(GeneModulationTarget):
    """
    Gene Knockout Target. Knockout a gene present in a COBRA model.
    """
    def __init__(self, id):
        super(GeneKnockoutTarget, self).__init__(id, 0, None)

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)
        target.knock_out(time_machine=time_machine)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, GeneModulationTarget) and not isinstance(other, GeneKnockoutTarget):
                return self.fold_change > other.fold_change
            elif isinstance(other, GeneKnockoutTarget):
                return False
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, GeneKnockoutTarget):
            return self.id == other.id
        elif isinstance(other, GeneModulationTarget):
            return self.id == other.id and other._value == 0
        else:
            return False

    def __str__(self):
        return "<GeneKnockout %s>" % self.id

class ReactionModulationTarget(FluxModulationTarget):
    __gnomic_feature_type__ = "reaction"

    def __init__(self, id, value, reference_value):
        super(ReactionModulationTarget, self).__init__(id, value, reference_value)

    def get_model_target(self, model):
        return model.reactions.get_by_id(self.id)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockinTarget):
                if self._value == 0:
                    raise IncompatibleTargets(self, other)
                else:
                    return True
            elif isinstance(other, ReactionCofactorSwapTarget):
                return True
            elif isinstance(other, ReactionModulationTarget) and not isinstance(other, ReactionKnockoutTarget):
                return self.fold_change > other.fold_change
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionModulationTarget):
            return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value
        else:
            return False

class ReactionKnockoutTarget(ReactionModulationTarget):
    """
    Reaction Knockout Target. Knockout a reaction present in a COBRA model.
    """
    def __init__(self, id):
        super(ReactionKnockoutTarget, self).__init__(id, 0, None)

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)
        target.knock_out(time_machine=time_machine)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionModulationTarget):
                if other._value == 0:
                    return True
                else:
                    raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionCofactorSwapTarget):
                raise IncompatibleTargets(self, other)
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionKnockoutTarget):
            return self.id == other.id
        elif isinstance(other, ReactionModulationTarget):
            return self.id == other.id and other._value == 0
        else:
            return False

    def __str__(self):
        return "<ReactionKnockout %s>" % self.id

class ReactionInversionTarget(ReactionModulationTarget):

    def __str__(self):
        return "<ReactionInversion %s (%.5f -> %.5f)>" % (self.id, self._reference_value, self._value)

    def _repr_html_(self):
        return "inv-%s" % self.id

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionModulationTarget):
                if other._value == self._value:
                    return True
                else:
                    raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionKnockinTarget):
                return True
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionInversionTarget):
            return self.id == other.id and other._value == self._value
        else:
            return False

    def apply(self, model, time_machine=None):
        reaction = self.get_model_target(model)
        reverse_flux(reaction, self._reference_value, self._value, time_machine=time_machine)

class EnsembleTarget(Target):
    """
    A Target resulting of merging multiple Targets.
    For example:
        1. ReactionKnockinTarget
        2. ReactionModulationTarget

    The Targets are prioritized by what should happen first in order to don't break.

    """
    def __init__(self, id, targets):
        super(EnsembleTarget, self).__init__(id)
        assert all(t.id == id for t in targets)
        self.targets = list(sorted(targets))

    def apply(self, model, time_machine=None):
        for target in self.targets:
            target.apply(model, time_machine=time_machine)

    def __str__(self):
        head = "<EsembleTarget %s" % self.id
        body = "\n\t".join("%i - %s" % (i, str(target)) for i, target in enumerate(self.targets))
        end = ">"

        return head + "\n" + body + "\n" + end

    # TODO implement gnomic compatibility
    def to_gnomic(self):
        raise NotImplementedError

    def _repr_html_(self):
        return "|" + ";".join(target._repr_html_() for target in self.targets) + '|'
<commit_msg>fix:give a list to remove reactions so it doesn't display warnings (#110)<commit_after># Copyright 2016 The Novo Nordisk Foundation Center for Biosustainability, DTU.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from functools import partial

import numpy

from cameo.core.manipulation import swap_cofactors, increase_flux, decrease_flux, reverse_flux

try:
    from gnomic import Accession, Feature, Del, Mutation, Sub, Ins
    _gnomic_available_ = True
except (ImportError, SyntaxError):  # SyntaxError from py2 incompatible syntax
    _gnomic_available_ = False

from cameo import ui
from cameo.exceptions import IncompatibleTargets

__all__ = ["GeneModulationTarget", "GeneKnockoutTarget", "ReactionCofactorSwapTarget", "ReactionKnockinTarget",
           "ReactionKnockoutTarget", "ReactionModulationTarget"]

# TODO: This should be ABC.
class Target(object):
    """
    A Target is element of a COBRA model that will change to achieve a given phenotype.
    They are identified by StrainDesignMethod.run when identifying which manipulations
    are necessary to improve some fitness function.

    Attributes
    ----------
    id: str
        The identifier of the target. The id must be present in the COBRA model.

    """
    def __init__(self, id):
        self.id = id

    def apply(self, model, time_machine=None):
        """
        Applies the modification on the target, depending on the target type.

        See Also
        --------
        Subclass implementations

        """
        raise NotImplementedError

    def __eq__(self, other):
        if isinstance(other, Target):
            return other.id == self.id
        else:
            return False

    def __gt__(self, other):
        return self.id > other.id

    def to_gnomic(self):
        """
        If gnomic is available, return a Gnomic representation of the Target.

        """
        if _gnomic_available_:
            return Accession(identifier=self.id)
        else:
            raise SystemError("Gnomic is only compatible with python >= 3 (%i.%i)" %
                              (sys.version_info.major, sys.version_info.minor))

    def __str__(self):
        return self.id

class FluxModulationTarget(Target):
    """
    A Target that changes the flux constraints (knockouts, over-expression or down-regulation).

    See Also
    --------
    ReactionModulationTarget, ReactionKnockoutTarget, GeneModulationTarget and GeneKnockoutTarget for implementation.

    """
    __gnomic_feature_type__ = 'flux'

    def __init__(self, id, value, reference_value):
        super(FluxModulationTarget, self).__init__(id)
        self._value = value
        self._reference_value = reference_value

    def get_model_target(self, model):
        raise NotImplementedError

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)

        if self._value == 0:
            target.knock_out(time_machine=time_machine)
        elif abs(self._value) > abs(self._reference_value):
            increase_flux(target, self._reference_value, self._value, time_machine=time_machine)
        elif abs(self._value) < abs(self._reference_value):
            decrease_flux(target, self._reference_value, self._value, time_machine=time_machine)

    def __eq__(self, other):
        if isinstance(other, FluxModulationTarget):
            return self.id == other.id and self._value == other._value
        else:
            return False

    @property
    def fold_change(self):
        """
        (B - A)/A

        Return
        ------
            float
        """
        try:
            return (self._reference_value - self._value) / self._value
        except ZeroDivisionError:
            return numpy.inf

    def __str__(self):
        if self._value == 0:
            return ui.delta() + self.id
        elif self._value > self._reference_value:
            return ui.upreg(self.fold_change) + self.id
        elif self._value < self._reference_value:
            return ui.downreg(self.fold_change) + self.id

    def _repr_html_(self):
        if self._value == 0:
            return "Δ%s" % self.id
        elif self._value > self._reference_value:
            return "↑(%.3f)%s" % (self.fold_change, self.id)
        elif self._value < self._reference_value:
            return "↓(%.3f)%s" % (self.fold_change, self.id)

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        feature = Feature(accession=accession, type=self.__gnomic_feature_type__)
        if self._value == 0:
            return Del(feature)
        elif abs(self._value) > abs(self._reference_value):
            over_expression = Feature(accession=accession, type=self.__gnomic_feature_type__,
                                      variant="over-expression(%.3f)" % self.fold_change)
            return Mutation(feature, over_expression)
        elif abs(self._value) < abs(self._reference_value):
            under_expression = Feature(accession=accession, type=self.__gnomic_feature_type__,
                                       variant="down-regulation(%.3f)" % self.fold_change)
            return Mutation(feature, under_expression)

class ReactionCofactorSwapTarget(Target):
    """
    Swap cofactors of a given reaction.
    """
    def __init__(self, id, swap_pairs):
        super(ReactionCofactorSwapTarget, self).__init__(id)
        self.swap_pairs = swap_pairs

    def apply(self, model, time_machine=None):
        reaction = model.reactions.get_by_id(self.id)
        swap_cofactors(reaction, model, self.swap_pairs, time_machine=time_machine)

    @property
    def swap_str(self):
        return "+".join(str(s) for s in self.swap_pairs[0]) + "<->" + "+".join(str(s) for s in self.swap_pairs[1])

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        new_accession = Accession(self.id + self.swap_str)
        original_feature = Feature(accession=accession, type='reaction')
        new_feature = Feature(accession=new_accession, type='reaction')
        return Sub(original_feature, new_feature)

    def __str__(self):
        return "<ReactionCofactorSwap %s swap=%s>" % (self.id, self.swap_str)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            if isinstance(other, ReactionModulationTarget):
                return False
            if isinstance(other, ReactionKnockinTarget):
                return True
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionCofactorSwapTarget):
            return self.id == other.id and self.swap_pairs == other.swap_pairs
        else:
            return False

    def _repr_html_(self):
        return str(self).replace("<->", "⇄")

class KnockinTarget(Target):
    def __init__(self, id, value):
        super(KnockinTarget, self).__init__(id)
        self._value = value

    def to_gnomic(self):
        raise NotImplementedError

class ReactionKnockinTarget(KnockinTarget):
    def __init__(self, id, value):
        super(ReactionKnockinTarget, self).__init__(id, value)

    def apply(self, model, time_machine=None):
        if time_machine is None:
            model.add_reaction(self._value)
        else:
            time_machine(do=partial(model.add_reaction, self._value),
                         undo=partial(model.remove_reactions, [self._value], delete=False, remove_orphans=True))

    def to_gnomic(self):
        accession = Target.to_gnomic(self)
        feature = Feature(accession=accession, type='reaction')
        return Ins(feature)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionModulationTarget):
                if other._value == 0:
                    raise IncompatibleTargets(self, other)
                else:
                    return False
            elif isinstance(other, ReactionKnockinTarget):
                return False
            elif isinstance(other, ReactionCofactorSwapTarget):
                return False
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionKnockinTarget):
            return self.id == other.id
        else:
            return False

    def __str__(self):
        return "<ReactionKnockin %s>" % self.id

    def _repr_html_(self):
        return "::%s" % self.id

class GeneModulationTarget(FluxModulationTarget):
    __gnomic_feature_type__ = "gene"

    def __init__(self, id, value, reference_value):
        super(GeneModulationTarget, self).__init__(id, value, reference_value)

    def get_model_target(self, model):
        return model.genes.get_by_id(self.id)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, GeneKnockoutTarget):
                return False
            elif isinstance(other, GeneModulationTarget) and not isinstance(other, GeneKnockoutTarget):
                return self.fold_change > other.fold_change
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, GeneModulationTarget):
            return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value
        else:
            return False

class GeneKnockoutTarget(GeneModulationTarget):
    """
    Gene Knockout Target. Knockout a gene present in a COBRA model.
    """
    def __init__(self, id):
        super(GeneKnockoutTarget, self).__init__(id, 0, None)

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)
        target.knock_out(time_machine=time_machine)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, GeneModulationTarget) and not isinstance(other, GeneKnockoutTarget):
                return self.fold_change > other.fold_change
            elif isinstance(other, GeneKnockoutTarget):
                return False
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, GeneKnockoutTarget):
            return self.id == other.id
        elif isinstance(other, GeneModulationTarget):
            return self.id == other.id and other._value == 0
        else:
            return False

    def __str__(self):
        return "<GeneKnockout %s>" % self.id

class ReactionModulationTarget(FluxModulationTarget):
    __gnomic_feature_type__ = "reaction"

    def __init__(self, id, value, reference_value):
        super(ReactionModulationTarget, self).__init__(id, value, reference_value)

    def get_model_target(self, model):
        return model.reactions.get_by_id(self.id)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionKnockinTarget):
                if self._value == 0:
                    raise IncompatibleTargets(self, other)
                else:
                    return True
            elif isinstance(other, ReactionCofactorSwapTarget):
                return True
            elif isinstance(other, ReactionModulationTarget) and not isinstance(other, ReactionKnockoutTarget):
                return self.fold_change > other.fold_change
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionModulationTarget):
            return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value
        else:
            return False

class ReactionKnockoutTarget(ReactionModulationTarget):
    """
    Reaction Knockout Target. Knockout a reaction present in a COBRA model.
    """
    def __init__(self, id):
        super(ReactionKnockoutTarget, self).__init__(id, 0, None)

    def apply(self, model, time_machine=None):
        target = self.get_model_target(model)
        target.knock_out(time_machine=time_machine)

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionModulationTarget):
                if other._value == 0:
                    return True
                else:
                    raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionCofactorSwapTarget):
                raise IncompatibleTargets(self, other)
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionKnockoutTarget):
            return self.id == other.id
        elif isinstance(other, ReactionModulationTarget):
            return self.id == other.id and other._value == 0
        else:
            return False

    def __str__(self):
        return "<ReactionKnockout %s>" % self.id

class ReactionInversionTarget(ReactionModulationTarget):

    def __str__(self):
        return "<ReactionInversion %s (%.5f -> %.5f)>" % (self.id, self._reference_value, self._value)

    def _repr_html_(self):
        return "inv-%s" % self.id

    def __gt__(self, other):
        if self.id == other.id:
            if isinstance(other, ReactionModulationTarget):
                if other._value == self._value:
                    return True
                else:
                    raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionKnockoutTarget):
                raise IncompatibleTargets(self, other)
            elif isinstance(other, ReactionKnockinTarget):
                return True
            else:
                raise IncompatibleTargets(self, other)
        else:
            return self.id > other.id

    def __eq__(self, other):
        if isinstance(other, ReactionInversionTarget):
            return self.id == other.id and other._value == self._value
        else:
            return False

    def apply(self, model, time_machine=None):
        reaction = self.get_model_target(model)
        reverse_flux(reaction, self._reference_value, self._value, time_machine=time_machine)

class EnsembleTarget(Target):
    """
    A Target resulting of merging multiple Targets.
    For example:
        1. ReactionKnockinTarget
        2. ReactionModulationTarget

    The Targets are prioritized by what should happen first in order to don't break.

    """
    def __init__(self, id, targets):
        super(EnsembleTarget, self).__init__(id)
        assert all(t.id == id for t in targets)
        self.targets = list(sorted(targets))

    def apply(self, model, time_machine=None):
        for target in self.targets:
            target.apply(model, time_machine=time_machine)

    def __str__(self):
        head = "<EsembleTarget %s" % self.id
        body = "\n\t".join("%i - %s" % (i, str(target)) for i, target in enumerate(self.targets))
        end = ">"

        return head + "\n" + body + "\n" + end

    # TODO implement gnomic compatibility
    def to_gnomic(self):
        raise NotImplementedError

    def _repr_html_(self):
        return "|" + ";".join(target._repr_html_() for target in self.targets) + '|'
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import datetime

from sqlalchemy import create_engine
from flask.ext.babel import lazy_gettext
from flask.globals import current_app
from sqlalchemy.exc import OperationalError

from werkzeug.local import LocalProxy

from model.constants import WEEKDAYS

from sipa.utils import timetag_from_timestamp, timestamp_from_timetag
from sipa.utils.exceptions import DBQueryEmpty
from .ldap_utils import get_current_uid

import logging
logger = logging.getLogger(__name__)

def init_db(app):
    app.extensions['db_atlantis'] = create_engine(
        'mysql+pymysql://{0}:{1}@{2}:3306/netusers'.format(
            app.config['DB_ATLANTIS_USER'],
            app.config['DB_ATLANTIS_PASSWORD'],
            app.config['DB_ATLANTIS_HOST']),
        echo=False, connect_args={'connect_timeout': app.config['SQL_TIMEOUT']}
    )
    app.extensions['db_helios'] = create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}/'.format(
            app.config['DB_HELIOS_USER'],
            app.config['DB_HELIOS_PASSWORD'],
            app.config['DB_HELIOS_HOST'],
            int(app.config['DB_HELIOS_PORT'])),
        echo=False, connect_args={'connect_timeout': app.config['SQL_TIMEOUT']}
    )

db_atlantis = LocalProxy(lambda: current_app.extensions['db_atlantis'])
db_helios = LocalProxy(lambda: current_app.extensions['db_helios'])

DORMITORIES = [
    'Wundstraße 5',
    'Wundstraße 7',
    'Wundstraße 9',
    'Wundstraße 11',
    'Wundstraße 1',
    'Wundstraße 3',
    'Zellescher Weg 41',
    'Zellescher Weg 41A',
    'Zellescher Weg 41B',
    'Zellescher Weg 41C',
    'Zellescher Weg 41D',
    'Borsbergstraße 34',
]

STATUS = {
    # todo vervollständigen oder mindestens fehlerresistent machen!
    # (Hat ein Nutzer einen unten nicht enthaltenen Status, gibts einen Fehler)
    1: lazy_gettext('Bezahlt, verbunden'),
    2: lazy_gettext('Nicht bezahlt, Netzanschluss gesperrt'),
    7: lazy_gettext('Verstoß gegen Netzordnung, Netzanschluss gesperrt'),
    9: lazy_gettext('Exaktiv'),
    12: lazy_gettext('Trafficlimit überschritten, Netzanschluss gesperrt')
}

def sql_query(query, args=(), database=db_atlantis):
    """Prepare and execute a raw sql query.
    'args' is a tuple needed for string replacement.
    """
    conn = database.connect()
    result = conn.execute(query, args)
    conn.close()
    return result

def status_string_from_id(status_id):
    return STATUS.get(status_id, lazy_gettext("Unbekannt"))

def user_id_from_uid(uid=None):
    """Fetch user.id (MySQL) from user.uid (LDAP)

    :param uid: The uid of the LDAP user object
    :return: The user id of the MySQL user
    """
    if uid is None:
        uid = get_current_uid()

    return sql_query("SELECT nutzer_id FROM nutzer WHERE unix_account = %s",
                     (uid,)).fetchone()['nutzer_id']

def ip_from_user_id(user_id):
    result = (sql_query(
        "SELECT c_ip FROM computer WHERE nutzer_id = %s",
        (user_id,)
    ).fetchone())
    if not result:
        raise DBQueryEmpty('Nutzer hat keine IP')
    return result['c_ip']

def user_id_from_ip(ip):
    """Returns the MySQL user.id corresponding to the ip or 0 if foreign to the db
    :param ip: A valid IP address
    :return: A user id
    """

    result = sql_query("SELECT nutzer_id FROM computer WHERE c_ip = %s",
                       (ip,)).fetchone()
    if result is None:
        return 0

    return result['nutzer_id']

def query_current_credit(uid=None, ip=None):
    """Returns the current credit in MiB
    :param uid: The id of the user
    :param ip: The ip of the user
    :return: The current amount of credit or False if foreign IP
    """
    if uid is None:
        if ip is None:
            raise AttributeError('Either ip or user_id must be specified!')
        user_id = user_id_from_ip(ip)
        if user_id is 0:
            return False  # IP doesn't correspond to any user
    else:
        user_id = user_id_from_uid(uid)
        ip = ip_from_user_id(user_id)

    try:
        result = sql_query(
            "SELECT amount - input - output as current "
            "FROM traffic.tuext AS t "
            "LEFT OUTER JOIN credit AS c ON t.timetag = c.timetag "
            "WHERE ip = %(ip)s AND c.user_id = %(user_id)s "
            "AND t.timetag = %(today)s",
            {'today': timetag_from_timestamp(), 'ip': ip, 'user_id': user_id}
        ).fetchone()
    except OperationalError as e:
        logger.critical('Unable to connect to MySQL server',
                        extra={'data': {'exception_args': e.args}})
        raise
    else:
        return round(result['current'] / 1024, 2)

def query_trafficdata(ip, user_id):
    """Query traffic input/output for IP

    :param ip: a valid ip
    :param user_id: an id of a mysql user tuple
    :return: a dict containing the traffic data in the form of
    {'history': [('weekday', in, out, credit), …], 'credit': credit}
    """
    trafficdata = sql_query(
        "SELECT t.timetag - %(today)s AS day, input, output, amount "
        "FROM traffic.tuext AS t "
        "LEFT OUTER JOIN credit AS c ON t.timetag = c.timetag "
        "WHERE ip = %(ip)s AND c.user_id = %(uid)s "
        "AND t.timetag BETWEEN %(weekago)s AND %(today)s "
        "ORDER BY 'day' DESC ",
        {'today': timetag_from_timestamp(),
         'weekago': timetag_from_timestamp() - 6,
         'ip': ip,
         'uid': user_id}
    ).fetchall()

    if not trafficdata:
        raise DBQueryEmpty('No trafficdata retrieved for user {}@{}'
                           .format(user_id, ip))

    traffic = {'history': [], 'credit': 0}
    returned_days = [int(i['day']) for i in trafficdata]

    # loop through expected days ([-6..0])
    for d in range(-6, 1):
        day = datetime.date.fromtimestamp(
            timestamp_from_timetag(timetag_from_timestamp() + d)
        ).strftime('%w')
        if d in returned_days:
            # pick the to `d` corresponding item of the mysql-result
            i = next((x for x in trafficdata if x['day'] == d), None)

            (input, output, credit) = (
                round(i[param] / 1024.0, 2)
                for param in ['input', 'output', 'amount']
            )
            traffic['history'].append(
                (WEEKDAYS[day], input, output, credit))
        else:
            traffic['history'].append(
                (WEEKDAYS[day], 0.0, 0.0, 0.0))

    traffic['credit'] = (lambda x: x[3] - x[1] - x[2])(traffic['history'][-1])

    return traffic

def update_macaddress(ip, oldmac, newmac):
    """Update a MAC address in computer table.

    TODO: check, if 'LIMIT 1' causes problems (sqlalchemy says
    "Warning: Unsafe statement")
    """
    # todo why does one the old mac_address?
    sql_query(
        "UPDATE computer "
        "SET c_etheraddr = %s "
        "WHERE c_ip = %s "
        "AND c_etheraddr = %s "
        "LIMIT 1",
        (newmac.lower(), ip, oldmac)
    )

def user_has_mysql_db(username):
    """Returns true if a database with the given name exists on
    helios-userdatabase, otherwise false.
    """
    userdb = sql_query(
        "SELECT SCHEMA_NAME "
        "FROM INFORMATION_SCHEMA.SCHEMATA "
        "WHERE SCHEMA_NAME = %s",
        (username,),
        database=db_helios
    ).fetchone()

    if userdb is not None:
        return True
    return False

def create_mysql_userdatabase(username, password):
    """A user specific database on helios is going to be created.
    """
    sql_query(
        "CREATE DATABASE "
        "IF NOT EXISTS `%s`" % username,
        database=db_helios
    )

    change_mysql_userdatabase_password(username, password)

def change_mysql_userdatabase_password(username, password):
    """This changes a user password for the helios MySQL-database.
    """
    user = sql_query(
        "SELECT user "
        "FROM mysql.user "
        "WHERE user = %s",
        (username,),
        database=db_helios
    ).fetchall()

    if not user:
        sql_query(
            "CREATE USER %s@'10.1.7.%%' "
            "IDENTIFIED BY %s",
            (username, password),
            database=db_helios
        )
    else:
        sql_query(
            "SET PASSWORD "
            "FOR %s@'10.1.7.%%' = PASSWORD(%s)",
            (username, password),
            database=db_helios
        )

    sql_query(
        "GRANT SELECT, INSERT, UPDATE, DELETE, "
        "ALTER, CREATE, DROP, INDEX, LOCK TABLES "
        "ON `%s`.* "
        "TO %%s@'10.1.7.%%%%'" % username,
        (username,),
        database=db_helios
    )

def drop_mysql_userdatabase(username):
    """This removes a userdatabase on helios.
    """
    sql_query(
        "DROP DATABASE "
        "IF EXISTS `%s`" % username,
        database=db_helios
    )

    sql_query(
        "DROP USER %s@'10.1.7.%%'",
        (username,),
        database=db_helios
    )

def calculate_userid_checksum(user_id):
    """Calculate checksum for a userid.
    (Modulo 10 on the sum of all digits)

    :param user_id: The id of the mysql user tuple
    """
    return sum(map(int, str(user_id))) % 10
<commit_msg>Check if credit query result exists<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import datetime

from sqlalchemy import create_engine
from flask.ext.babel import lazy_gettext
from flask.globals import current_app
from sqlalchemy.exc import OperationalError

from werkzeug.local import LocalProxy

from model.constants import WEEKDAYS

from sipa.utils import timetag_from_timestamp, timestamp_from_timetag
from sipa.utils.exceptions import DBQueryEmpty
from .ldap_utils import get_current_uid

import logging
logger = logging.getLogger(__name__)

def init_db(app):
    app.extensions['db_atlantis'] = create_engine(
        'mysql+pymysql://{0}:{1}@{2}:3306/netusers'.format(
            app.config['DB_ATLANTIS_USER'],
            app.config['DB_ATLANTIS_PASSWORD'],
            app.config['DB_ATLANTIS_HOST']),
        echo=False, connect_args={'connect_timeout': app.config['SQL_TIMEOUT']}
    )
    app.extensions['db_helios'] = create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}/'.format(
            app.config['DB_HELIOS_USER'],
            app.config['DB_HELIOS_PASSWORD'],
            app.config['DB_HELIOS_HOST'],
            int(app.config['DB_HELIOS_PORT'])),
        echo=False, connect_args={'connect_timeout': app.config['SQL_TIMEOUT']}
    )

db_atlantis = LocalProxy(lambda: current_app.extensions['db_atlantis'])
db_helios = LocalProxy(lambda: current_app.extensions['db_helios'])

DORMITORIES = [
    'Wundstraße 5',
    'Wundstraße 7',
    'Wundstraße 9',
    'Wundstraße 11',
    'Wundstraße 1',
    'Wundstraße 3',
    'Zellescher Weg 41',
    'Zellescher Weg 41A',
    'Zellescher Weg 41B',
    'Zellescher Weg 41C',
    'Zellescher Weg 41D',
    'Borsbergstraße 34',
]

STATUS = {
    # todo vervollständigen oder mindestens fehlerresistent machen!
    # (Hat ein Nutzer einen unten nicht enthaltenen Status, gibts einen Fehler)
    1: lazy_gettext('Bezahlt, verbunden'),
    2: lazy_gettext('Nicht bezahlt, Netzanschluss gesperrt'),
    7: lazy_gettext('Verstoß gegen Netzordnung, Netzanschluss gesperrt'),
    9: lazy_gettext('Exaktiv'),
    12: lazy_gettext('Trafficlimit überschritten, Netzanschluss gesperrt')
}

def sql_query(query, args=(), database=db_atlantis):
    """Prepare and execute a raw sql query.
    'args' is a tuple needed for string replacement.
    """
    conn = database.connect()
    result = conn.execute(query, args)
    conn.close()
    return result

def status_string_from_id(status_id):
    return STATUS.get(status_id, lazy_gettext("Unbekannt"))

def user_id_from_uid(uid=None):
    """Fetch user.id (MySQL) from user.uid (LDAP)

    :param uid: The uid of the LDAP user object
    :return: The user id of the MySQL user
    """
    if uid is None:
        uid = get_current_uid()

    return sql_query("SELECT nutzer_id FROM nutzer WHERE unix_account = %s",
                     (uid,)).fetchone()['nutzer_id']

def ip_from_user_id(user_id):
    result = (sql_query(
        "SELECT c_ip FROM computer WHERE nutzer_id = %s",
        (user_id,)
    ).fetchone())
    if not result:
        raise DBQueryEmpty('Nutzer hat keine IP')
    return result['c_ip']

def user_id_from_ip(ip):
    """Returns the MySQL user.id corresponding to the ip or 0 if foreign to the db
    :param ip: A valid IP address
    :return: A user id
    """

    result = sql_query("SELECT nutzer_id FROM computer WHERE c_ip = %s",
                       (ip,)).fetchone()
    if result is None:
        return 0

    return result['nutzer_id']

def query_current_credit(uid=None, ip=None):
    """Returns the current credit in MiB
    :param uid: The id of the user
    :param ip: The ip of the user
    :return: The current amount of credit or False if foreign IP
    """
    if uid is None:
        if ip is None:
            raise AttributeError('Either ip or user_id must be specified!')
        user_id = user_id_from_ip(ip)
        if user_id is 0:
            return False  # IP doesn't correspond to any user
    else:
        user_id = user_id_from_uid(uid)
        ip = ip_from_user_id(user_id)

    try:
        result = sql_query(
            "SELECT amount - input - output as current "
            "FROM traffic.tuext AS t "
            "LEFT OUTER JOIN credit AS c ON t.timetag = c.timetag "
            "WHERE ip = %(ip)s AND c.user_id = %(user_id)s "
            "AND t.timetag = %(today)s",
            {'today': timetag_from_timestamp(), 'ip': ip, 'user_id': user_id}
        ).fetchone()
    except OperationalError as e:
        logger.critical('Unable to connect to MySQL server',
                        extra={'data': {'exception_args': e.args}})
        raise
    else:
        if result and 'current' in result.keys():
            return round(result['current'] / 1024, 2)
        else:
            logger.warning("Credit query was empty", extra={'data': {
                'today': timetag_from_timestamp(), 'ip': ip, 'user_id': user_id
            }})

def query_trafficdata(ip, user_id):
    """Query traffic input/output for IP

    :param ip: a valid ip
    :param user_id: an id of a mysql user tuple
    :return: a dict containing the traffic data in the form of
    {'history': [('weekday', in, out, credit), …], 'credit': credit}
    """
    trafficdata = sql_query(
        "SELECT t.timetag - %(today)s AS day, input, output, amount "
        "FROM traffic.tuext AS t "
        "LEFT OUTER JOIN credit AS c ON t.timetag = c.timetag "
        "WHERE ip = %(ip)s AND c.user_id = %(uid)s "
        "AND t.timetag BETWEEN %(weekago)s AND %(today)s "
        "ORDER BY 'day' DESC ",
        {'today': timetag_from_timestamp(),
         'weekago': timetag_from_timestamp() - 6,
         'ip': ip,
         'uid': user_id}
    ).fetchall()

    if not trafficdata:
        raise DBQueryEmpty('No trafficdata retrieved for user {}@{}'
                           .format(user_id, ip))

    traffic = {'history': [], 'credit': 0}
    returned_days = [int(i['day']) for i in trafficdata]

    # loop through expected days ([-6..0])
    for d in range(-6, 1):
        day = datetime.date.fromtimestamp(
            timestamp_from_timetag(timetag_from_timestamp() + d)
        ).strftime('%w')
        if d in returned_days:
            # pick the to `d` corresponding item of the mysql-result
            i = next((x for x in trafficdata if x['day'] == d), None)

            (input, output, credit) = (
                round(i[param] / 1024.0, 2)
                for param in ['input', 'output', 'amount']
            )
            traffic['history'].append(
                (WEEKDAYS[day], input, output, credit))
        else:
            traffic['history'].append(
                (WEEKDAYS[day], 0.0, 0.0, 0.0))

    traffic['credit'] = (lambda x: x[3] - x[1] - x[2])(traffic['history'][-1])

    return traffic

def update_macaddress(ip, oldmac, newmac):
    """Update a MAC address in computer table.

    TODO: check, if 'LIMIT 1' causes problems (sqlalchemy says
    "Warning: Unsafe statement")
    """
    # todo why does one the old mac_address?
    sql_query(
        "UPDATE computer "
        "SET c_etheraddr = %s "
        "WHERE c_ip = %s "
        "AND c_etheraddr = %s "
        "LIMIT 1",
        (newmac.lower(), ip, oldmac)
    )

def user_has_mysql_db(username):
    """Returns true if a database with the given name exists on
    helios-userdatabase, otherwise false.
    """
    userdb = sql_query(
        "SELECT SCHEMA_NAME "
        "FROM INFORMATION_SCHEMA.SCHEMATA "
        "WHERE SCHEMA_NAME = %s",
        (username,),
        database=db_helios
    ).fetchone()

    if userdb is not None:
        return True
    return False

def create_mysql_userdatabase(username, password):
    """A user specific database on helios is going to be created.
    """
    sql_query(
        "CREATE DATABASE "
        "IF NOT EXISTS `%s`" % username,
        database=db_helios
    )

    change_mysql_userdatabase_password(username, password)

def change_mysql_userdatabase_password(username, password):
    """This changes a user password for the helios MySQL-database.
    """
    user = sql_query(
        "SELECT user "
        "FROM mysql.user "
        "WHERE user = %s",
        (username,),
        database=db_helios
    ).fetchall()

    if not user:
        sql_query(
            "CREATE USER %s@'10.1.7.%%' "
            "IDENTIFIED BY %s",
            (username, password),
            database=db_helios
        )
    else:
        sql_query(
            "SET PASSWORD "
            "FOR %s@'10.1.7.%%' = PASSWORD(%s)",
            (username, password),
            database=db_helios
        )

    sql_query(
        "GRANT SELECT, INSERT, UPDATE, DELETE, "
        "ALTER, CREATE, DROP, INDEX, LOCK TABLES "
        "ON `%s`.* "
        "TO %%s@'10.1.7.%%%%'" % username,
        (username,),
        database=db_helios
    )

def drop_mysql_userdatabase(username):
    """This removes a userdatabase on helios.
    """
    sql_query(
        "DROP DATABASE "
        "IF EXISTS `%s`" % username,
        database=db_helios
    )

    sql_query(
        "DROP USER %s@'10.1.7.%%'",
        (username,),
        database=db_helios
    )

def calculate_userid_checksum(user_id):
    """Calculate checksum for a userid.
    (Modulo 10 on the sum of all digits)

    :param user_id: The id of the mysql user tuple
    """
    return sum(map(int, str(user_id))) % 10
<|endoftext|>