<commit_before>import requests

from flask import current_app
from cryptokit.rpc import CoinserverRPC, CoinRPCException
from cryptokit.base58 import address_version
from decimal import Decimal as dec
from urlparse import urljoin

from . import models as m
from . import cache, redis_conn, currencies, exchanges, chains, powerpools, locations
from .exceptions import ConfigurationException, RemoteException


class ConfigChecker(object):
    """
    This class provides various methods for validating config values and checks
    configuration values and makes sure they're properly filled out.

    It needs a lot of expansion :/

    Currently validates the following keys:
    pool_payout_addr
    currencies
    """

    def __init__(self, config):
        self.config = config

    def lookup_key(self, key, nested=None):
        """ Helper method: Checks the config for the specified key and raises
        an error if not found """

        try:
            if not nested:
                value = self.config[key]
            else:
                value = nested[key]
        except KeyError:
            raise ConfigurationException('Failed when looking up \'{}\' in the '
                                         'config. This key is required!'.format(key))
        else:
            return value

    def check_truthiness(self, val):
        """ Helper method: Checks a value for truthiness """
        if not val:
            raise ConfigurationException('Value \'{}\' is not truthy. This '
                                         'value is required!'.format(val))

    def check_type(self, val, obj_type):
        """ Helper method: Checks a value to make sure its the correct type """
        if not type(val) is obj_type:
            raise ConfigurationException("\'{}\' is not an instance of {}".format(val))

    def check_is_bcaddress(self, val):
        """ Helper method: Checks a value for truthiness """
        try:
            ver = address_version(val)
        except (KeyError, AttributeError):
            raise ConfigurationException("\'{}\' is not a valid bitcoin style "
                                         "address".format(val))
        return ver

    def parse_config(self):
        """ Go through config keys and perform the appropriate logic checks """

        # Check the GLOBAL 'pool_payout_addrfield'
        p_addr = self.lookup_key('pool_payout_addr')
        self.check_truthiness(p_addr)
        self.check_is_bcaddress(p_addr)

        # Check the 'currencies' key
        currencies = self.lookup_key('currencies')
        self.check_truthiness(currencies)
        self.check_type(currencies, dict)
        for curr_key, curr_cfg in currencies.iteritems():
            c_addr = self.lookup_key('pool_payout_addr', nested=currencies[curr_key])
            c_ver = self.lookup_key('address_version', nested=currencies[curr_key])
            self.check_type(c_ver, list)
            exchangeable = self.lookup_key('exchangeable', nested=currencies[curr_key])
            # If a pool payout addr is specified, make sure it matches the
            # configured address version.
            if c_addr:
                ver = CurrencyKeeper.check_is_bcaddress(c_addr)
                if ver not in c_ver:
                    raise ConfigurationException("{} is not a valid {} address."
                                                 " Must be {}"
                                                 .format(c_addr, curr_key, c_ver))
            # Check for valid  pool address for unexchangeable currencies
            if not exchangeable and not c_addr:
                raise ConfigurationException(
                    "Unexchangeable currencies require a pool payout addr."
                    "No valid address found for {}".format(curr_key))


class ConfigObject(object):
    __getitem__ = lambda self, a: getattr(self, a)
    requires = []
    defaults = dict()

    def __init__(self, bootstrap):
        for req in self.requires:
            if req not in bootstrap:
                raise ConfigurationException(
                    "{} item requires {}".format(self.__class__.__name__, req))
        # Default settings
        self.__dict__.update(self.defaults)
        self.__dict__.update(bootstrap)

    def __str__(self):
        return self.key

    def __repr__(self):
        return self.key

    def __hash__(self):
        return hash(self.key)


class Currency(ConfigObject):
    requires = ['algo', 'name', 'coinserv', 'address_version',
                'trans_confirmations', 'block_time', 'block_mature_confirms']
    defaults = dict(exchangeable=False,
                    pool_payout_addr=None)

    def __init__(self, bootstrap):
        ConfigObject.__init__(self, bootstrap)
        self.coinserv = CoinserverRPC(
            "http://{0}:{1}@{2}:{3}/"
            .format(bootstrap['coinserv']['username'],
                    bootstrap['coinserv']['password'],
                    bootstrap['coinserv']['address'],
                    bootstrap['coinserv']['port'],
                    pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10))))

    @property
    @cache.memoize(timeout=3600)
    def btc_value(self):
        """ Caches and returns estimated currency value in BTC """
        if self.key == "BTC":
            return dec('1')

        # XXX: Needs better number here!
        err, dat, _ = exchanges.optimal_sell(self.key, dec('1000'), exchanges._get_current_object().exchanges)
        try:
            current_app.logger.info("Got new average price of {} for {}"
                                    .format(dat['avg_price'], self))
            return dat['avg_price']
        except (KeyError, TypeError):
            current_app.logger.warning("Unable to grab price for currency {}, got {} from autoex!"
                                       .format(self.key, dict(err=err, dat=dat)))
            return dec('0')

    def est_value(self, other_currency, amount):
        val = self.btc_value
        if val:
            return amount * val / other_currency.btc_value
        return dec('0')

    def __repr__(self):
        return self.key
    __str__ = __repr__

    def __hash__(self):
        return self.key.__hash__()


class CurrencyKeeper(dict):
    __getattr__ = dict.__getitem__

    def __init__(self, currency_dictionary):
        super(CurrencyKeeper, self).__init__()
        for key, config in currency_dictionary.iteritems():
            config['key'] = key
            obj = Currency(config)
            if key in self:
                raise ConfigurationException("Duplicate currency keys {}"
                                             .format(key))
            self[obj.key] = obj


    @property
    def exchangeable_currencies(self):
        return [c for c in self.itervalues() if c.exchangeable is True]

    @property
    def unexchangeable_currencies(self):
        return [c for c in self.itervalues() if c.exchangeable is False]

    @property
    def available_currencies(self):
        return [k for k in self.iterkeys()]

    @property
    def available_versions(self):
        versions = {}<commit_msg>Fixes a few minor issues in configchecker<commit_after>import requests
import collections

from flask import current_app
from cryptokit.rpc import CoinserverRPC, CoinRPCException
from cryptokit.base58 import address_version
from decimal import Decimal as dec
from urlparse import urljoin

from . import models as m
from . import cache, redis_conn, currencies, exchanges, chains, powerpools, locations
from .exceptions import ConfigurationException, RemoteException


class ConfigChecker(object):
    """
    This class provides various methods for validating config values and checks
    configuration values and makes sure they're properly filled out.

    It needs a lot of expansion :/

    Currently validates the following keys:
    pool_payout_addr
    currencies
    """

    def __init__(self, config):
        self.config = config

    def lookup_key(self, key, nested=None):
        """ Helper method: Checks the config for the specified key and raises
        an error if not found """

        try:
            if nested is None:
                value = self.config[key]
            else:
                value = nested[key]
        except KeyError:
            raise ConfigurationException('Failed when looking up \'{}\' in the '
                                         'config. This key is required!'.format(key))
        else:
            return value

    def check_truthiness(self, val):
        """ Helper method: Checks a value for truthiness """
        if not val:
            raise ConfigurationException('Value \'{}\' is not truthy. This '
                                         'value is required!'.format(val))

    def check_type(self, val, obj_type):
        """ Helper method: Checks a value to make sure its the correct type """
        if not isinstance(val, obj_type):
            raise ConfigurationException("\'{}\' is not an instance of {}"
                                         .format(val, obj_type.__name__))

    def check_is_bcaddress(self, val):
        """ Helper method: Checks a value for truthiness """
        try:
            ver = address_version(val)
        except (KeyError, AttributeError):
            raise ConfigurationException("\'{}\' is not a valid bitcoin style "
                                         "address".format(val))
        return ver

    def parse_config(self):
        """ Go through config keys and perform the appropriate logic checks """
        # Check the GLOBAL 'pool_payout_addrfield'
        p_addr = self.lookup_key('pool_payout_addr')
        self.check_is_bcaddress(p_addr)

        # Check the 'currencies' key
        currencies = self.lookup_key('currencies')
        self.check_type(currencies, collections.Mapping)


class ConfigObject(object):
    __getitem__ = lambda self, a: getattr(self, a)
    requires = []
    defaults = dict()

    def __init__(self, bootstrap):
        for req in self.requires:
            if req not in bootstrap:
                raise ConfigurationException(
                    "{} item requires {}".format(self.__class__.__name__, req))
        # Default settings
        self.__dict__.update(self.defaults)
        self.__dict__.update(bootstrap)

    def __str__(self):
        return self.key

    def __repr__(self):
        return self.key

    def __hash__(self):
        return hash(self.key)


class Currency(ConfigObject):
    requires = ['algo', 'name', 'coinserv', 'address_version',
                'trans_confirmations', 'block_time', 'block_mature_confirms']
    defaults = dict(exchangeable=False,
                    pool_payout_addr=None)

    def __init__(self, bootstrap):
        ConfigObject.__init__(self, bootstrap)
        self.coinserv = CoinserverRPC(
            "http://{0}:{1}@{2}:{3}/"
            .format(bootstrap['coinserv']['username'],
                    bootstrap['coinserv']['password'],
                    bootstrap['coinserv']['address'],
                    bootstrap['coinserv']['port'],
                    pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10))))
        self.exchangeable = bool(self.exchangeable)

        # If a pool payout addr is specified, make sure it matches the
        # configured address version.
        if self.pool_payout_addr is not None:
            try:
                ver = address_version(self.pool_payout_addr)
            except (KeyError, AttributeError):
                ver = None
            if ver not in self.address_version:
                raise ConfigurationException(
                    "{} is not a valid {} address. Must be version {}"
                    .format(self.pool_payout_addr, self.key, self.address_version))

        # Check to make sure there is a configured pool address for
        # unexchangeable currencies
        if self.exchangeable is False and self.pool_payout_addr is None:
            raise ConfigurationException(
                "Unexchangeable currencies require a pool payout addr."
                "No valid address found for {}".format(self.key))

    @property
    @cache.memoize(timeout=3600)
    def btc_value(self):
        """ Caches and returns estimated currency value in BTC """
        if self.key == "BTC":
            return dec('1')

        # XXX: Needs better number here!
        err, dat, _ = exchanges.optimal_sell(self.key, dec('1000'), exchanges._get_current_object().exchanges)
        try:
            current_app.logger.info("Got new average price of {} for {}"
                                    .format(dat['avg_price'], self))
            return dat['avg_price']
        except (KeyError, TypeError):
            current_app.logger.warning("Unable to grab price for currency {}, got {} from autoex!"
                                       .format(self.key, dict(err=err, dat=dat)))
            return dec('0')

    def est_value(self, other_currency, amount):
        val = self.btc_value
        if val:
            return amount * val / other_currency.btc_value
        return dec('0')

    def __repr__(self):
        return self.key
    __str__ = __repr__

    def __hash__(self):
        return self.key.__hash__()


class CurrencyKeeper(dict):
    __getattr__ = dict.__getitem__

    def __init__(self, currency_dictionary):
        super(CurrencyKeeper, self).__init__()
        for key, config in currency_dictionary.iteritems():
            config['key'] = key
            obj = Currency(config)
            if key in self:
                raise ConfigurationException("Duplicate currency keys {}"
                                             .format(key))
            self[obj.key] = obj

    @property
    def exchangeable_currencies(self):
        return [c for c in self.itervalues() if c.exchangeable is True]

    @property
    def unexchangeable_currencies(self):
        return [c for c in self.itervalues() if c.exchangeable is False]

    @property
    def available_currencies(self):
        return [k for k in self.iterkeys()]

    @property
    def available_versions(self):
        versions = {}
{'fraction_non_alphanumeric': 0.09255718433538701, 'fraction_numerical': 0.002691027547624106, 'mean_word_length': 2.104418553528248, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2931060', 'n_tokens_mistral': 3795, 'n_tokens_neox': 3577, 'n_words': 1084}