Merge pull request #276 from lbryio/configuration

Better parse environment variables for configuration
This commit is contained in:
Job Evers‐Meltzer 2016-11-22 23:24:13 -06:00 committed by GitHub
commit 1695b26416
5 changed files with 131 additions and 117 deletions

View file

@ -6,6 +6,7 @@ import sys
import yaml import yaml
from appdirs import user_data_dir from appdirs import user_data_dir
import envparse
LBRYCRD_WALLET = 'lbrycrd' LBRYCRD_WALLET = 'lbrycrd'
LBRYUM_WALLET = 'lbryum' LBRYUM_WALLET = 'lbryum'
@ -41,68 +42,7 @@ else:
default_lbryum_dir = os.path.join(os.path.expanduser("~"), ".lbryum") default_lbryum_dir = os.path.join(os.path.expanduser("~"), ".lbryum")
def convert_setting(env_val, current_val): class Settings(object):
try:
return _convert_setting(env_val, current_val)
except Exception as exc:
log.warning(
'Failed to convert %s. Returning original: %s: %s',
env_val, current_val, exc)
return current_val
def _convert_setting(env_val, current_val):
new_type = env_val.__class__
current_type = current_val.__class__
if current_type is bool:
if new_type is bool:
return env_val
elif str(env_val).lower() == "false":
return False
elif str(env_val).lower() == "true":
return True
else:
raise ValueError('{} is not a valid boolean value'.format(env_val))
elif current_type is int:
return int(env_val)
elif current_type is float:
return float(env_val)
elif current_type is str:
return str(env_val)
elif current_type is unicode:
return unicode(env_val)
elif current_type is dict:
return dict(env_val)
elif current_type is list:
return list(env_val)
elif current_type is tuple:
return tuple(env_val)
else:
raise ValueError('Type {} cannot be converted'.format(current_type))
def convert_env_setting(setting, value):
try:
env_val = os.environ[setting]
except KeyError:
return value
else:
return convert_setting(env_val, value)
def get_env_settings(settings):
for setting, value in settings.iteritems():
setting = 'LBRY_' + setting.upper()
yield convert_env_setting(setting, value)
def add_env_settings_to_dict(settings_dict):
for setting, env_setting in zip(settings_dict, get_env_settings(settings_dict)):
settings_dict.update({setting: env_setting})
return settings_dict
class Setting(object):
"""A collection of configuration settings""" """A collection of configuration settings"""
__fixed = [] __fixed = []
__excluded = ['get_dict', 'update'] __excluded = ['get_dict', 'update']
@ -119,9 +59,7 @@ class Setting(object):
def __setitem__(self, key, value): def __setitem__(self, key, value):
assert key in self and key not in self.__fixed, KeyError(key) assert key in self and key not in self.__fixed, KeyError(key)
old_value = self[key] self.__dict__[key] = value
new_value = convert_setting(value, old_value)
self.__dict__[key] = new_value
def __contains__(self, item): def __contains__(self, item):
return item in iter(self) return item in iter(self)
@ -137,55 +75,124 @@ class Setting(object):
pass pass
class AdjustableSettings(Setting): class Env(envparse.Env):
"""Settings that are allowed to be overriden by the user""" """An Env parser that automatically namespaces the variables with LBRY"""
def __init__(self): NAMESPACE = 'LBRY_'
self.is_generous_host = True def __init__(self, **schema):
self.run_on_startup = False self.original_schema = schema
self.download_directory = default_download_directory my_schema = {
self.max_upload = 0.0 self._convert_key(key): self._convert_value(value)
self.max_download = 0.0 for key, value in schema.items()
self.upload_log = True }
self.delete_blobs_on_remove = True envparse.Env.__init__(self, **my_schema)
self.use_upnp = True
self.start_lbrycrdd = True def __call__(self, key, *args, **kwargs):
self.run_reflector_server = False my_key = self._convert_key(key)
self.startup_scripts = [] return super(Env, self).__call__(my_key, *args, **kwargs)
self.last_version = {'lbrynet': '0.0.1', 'lbryum': '0.0.1'}
self.peer_port = 3333 def _convert_key(self, key):
self.dht_node_port = 4444 return Env.NAMESPACE + key.upper()
self.reflector_port = 5566
self.download_timeout = 30 def _convert_value(self, value):
self.max_search_results = 25 """Allow value to be specified as an object, tuple or dict
self.search_timeout = 3.0
self.cache_time = 150 if object or dict, follow default envparse rules, if tuple
self.host_ui = True it needs to be of the form (cast, default) or (cast, default, subcast)
self.check_ui_requirements = True """
self.local_ui_path = False if isinstance(value, dict):
self.api_port = 5279 return value
self.data_rate = .0001 # points/megabyte if isinstance(value, (tuple, list)):
self.min_info_rate = .02 # points/1000 infos new_value = {'cast': value[0], 'default': value[1]}
self.min_valuable_info_rate = .05 # points/1000 infos if len(value) == 3:
self.min_valuable_hash_rate = .05 # points/1000 infos new_value['subcast'] = value[2]
self.max_connections_per_stream = 5 return new_value
self.known_dht_nodes = [ return value
def server_port(server_port):
server, port = server_port.split(':')
return server, port
DEFAULT_DHT_NODES = [
('lbrynet1.lbry.io', 4444), ('lbrynet1.lbry.io', 4444),
('lbrynet2.lbry.io', 4444), ('lbrynet2.lbry.io', 4444),
('lbrynet3.lbry.io', 4444) ('lbrynet3.lbry.io', 4444)
] ]
self.pointtrader_server = 'http://127.0.0.1:2424'
self.reflector_servers = [("reflector.lbry.io", 5566)]
self.wallet = LBRYUM_WALLET
self.ui_branch = "master"
self.default_ui_branch = 'master'
self.data_dir = default_data_dir
self.lbryum_wallet_dir = default_lbryum_dir
self.use_auth_http = False
self.sd_download_timeout = 3
self.max_key_fee = {'USD': {'amount': 25.0, 'address': ''}}
class ApplicationSettings(Setting): ENVIRONMENT = Env(
is_generous_host=(bool, True),
run_on_startup=(bool, False),
download_directory=(str, default_download_directory),
max_upload=(float, 0.0),
max_download=(float, 0.0),
upload_log=(bool, True),
delete_blobs_on_remove=(bool, True),
use_upnp=(bool, True),
start_lbrycrdd=(bool, True),
run_reflector_server=(bool, False),
startup_scripts=(list, []),
# TODO: this doesn't seem like the kind of thing that should
# be configured; move it elsewhere.
last_version=(dict, {'lbrynet': '0.0.1', 'lbryum': '0.0.1'}),
peer_port=(int, 3333),
dht_node_port=(int, 4444),
reflector_port=(int, 5566),
download_timeout=(int, 30),
max_search_results=(int, 25),
search_timeout=(float, 3.0),
cache_time=(int, 150),
host_ui=(bool, True),
check_ui_requirements=(bool, True),
local_ui_path=(bool, False),
api_port=(int, 5279),
search_servers=(list, ['lighthouse1.lbry.io:50005']),
data_rate=(float, .0001), # points/megabyte
min_info_rate=(float, .02), # points/1000 infos
min_valuable_info_rate=(float, .05), # points/1000 infos
min_valuable_hash_rate=(float, .05), # points/1000 infos
max_connections_per_stream=(int, 5),
known_dht_nodes=(list, DEFAULT_DHT_NODES, server_port),
pointtrader_server=(str, 'http://127.0.0.1:2424'),
reflector_servers=(list, [("reflector.lbry.io", 5566)], server_port),
wallet=(str, LBRYUM_WALLET),
ui_branch=(str, "master"),
default_ui_branch=(str, 'master'),
data_dir=(str, default_data_dir),
lbryum_wallet_dir=(str, default_lbryum_dir),
use_auth_http=(bool, False),
sd_download_timeout=(int, 3),
# TODO: this field is more complicated than it needs to be because
# it goes through a Fee validator when loaded by the exchange rate
# manager. Look into refactoring the exchange rate conversion to
# take in a simpler form.
#
# TODO: writing json on the cmd line is a pain, come up with a nicer
# parser for this data structure. (maybe MAX_KEY_FEE=USD:25
max_key_fee=(json.loads, {'USD': {'amount': 25.0, 'address': ''}})
)
class AdjustableSettings(Settings):
"""Settings that are allowed to be overriden by the user"""
def __init__(self, environ=None):
self.environ = environ or ENVIRONMENT
Settings.__init__(self)
def __getattr__(self, attr):
if attr in self.environ.original_schema:
return self.environ(attr)
raise AttributeError
def get_dict(self):
return {
name: self.environ(name)
for name in self.environ.original_schema
}
class ApplicationSettings(Settings):
"""Settings that are constants and shouldn't be overriden""" """Settings that are constants and shouldn't be overriden"""
def __init__(self): def __init__(self):
self.MAX_HANDSHAKE_SIZE = 64*KB self.MAX_HANDSHAKE_SIZE = 64*KB
@ -213,6 +220,7 @@ class ApplicationSettings(Setting):
self.LOGGLY_TOKEN = 'LJEzATH4AzRgAwxjAP00LwZ2YGx3MwVgZTMuBQZ3MQuxLmOv' self.LOGGLY_TOKEN = 'LJEzATH4AzRgAwxjAP00LwZ2YGx3MwVgZTMuBQZ3MQuxLmOv'
self.ANALYTICS_ENDPOINT = 'https://api.segment.io/v1' self.ANALYTICS_ENDPOINT = 'https://api.segment.io/v1'
self.ANALYTICS_TOKEN = 'Ax5LZzR1o3q3Z3WjATASDwR5rKyHH0qOIRIbLmMXn2H=' self.ANALYTICS_TOKEN = 'Ax5LZzR1o3q3Z3WjATASDwR5rKyHH0qOIRIbLmMXn2H='
Settings.__init__(self)
APPLICATION_SETTINGS = AdjustableSettings() APPLICATION_SETTINGS = AdjustableSettings()
@ -226,6 +234,11 @@ class DefaultSettings(ApplicationSettings, AdjustableSettings):
ApplicationSettings.__init__(self) ApplicationSettings.__init__(self)
AdjustableSettings.__init__(self) AdjustableSettings.__init__(self)
def get_dict(self):
d = ApplicationSettings.get_dict(self)
d.update(AdjustableSettings.get_dict(self))
return d
DEFAULT_SETTINGS = DefaultSettings() DEFAULT_SETTINGS = DefaultSettings()
@ -233,9 +246,6 @@ DEFAULT_SETTINGS = DefaultSettings()
class Config(DefaultSettings): class Config(DefaultSettings):
__shared_state = copy.deepcopy(DEFAULT_SETTINGS.get_dict()) __shared_state = copy.deepcopy(DEFAULT_SETTINGS.get_dict())
def __init__(self):
self.__dict__ = add_env_settings_to_dict(self.__shared_state)
@property @property
def ORIGIN(self): def ORIGIN(self):
return "http://%s:%i" % (DEFAULT_SETTINGS.API_INTERFACE, self.api_port) return "http://%s:%i" % (DEFAULT_SETTINGS.API_INTERFACE, self.api_port)

View file

@ -76,7 +76,7 @@ def start():
lbrynet_log = settings.get_log_filename() lbrynet_log = settings.get_log_filename()
log_support.configure_logging(lbrynet_log, args.logtoconsole, args.verbose) log_support.configure_logging(lbrynet_log, args.logtoconsole, args.verbose)
log.debug('Final Settings: %s', settings.__dict__) log.debug('Final Settings: %s', settings.get_dict())
try: try:
log.debug('Checking for an existing lbrynet daemon instance') log.debug('Checking for an existing lbrynet daemon instance')

View file

@ -55,6 +55,7 @@ C:\Python27\Scripts\pip.exe install colorama==0.3.7
C:\Python27\Scripts\pip.exe install dnspython==1.12.0 C:\Python27\Scripts\pip.exe install dnspython==1.12.0
C:\Python27\Scripts\pip.exe install ecdsa==0.13 C:\Python27\Scripts\pip.exe install ecdsa==0.13
C:\Python27\Scripts\pip.exe install envparse==0.2.0
C:\Python27\Scripts\pip.exe install jsonrpc==1.2 C:\Python27\Scripts\pip.exe install jsonrpc==1.2

View file

@ -5,6 +5,7 @@ argparse==1.2.1
colorama==0.3.7 colorama==0.3.7
dnspython==1.12.0 dnspython==1.12.0
ecdsa==0.13 ecdsa==0.13
envparse==0.2.0
gmpy==1.17 gmpy==1.17
jsonrpc==1.2 jsonrpc==1.2
jsonrpclib==0.1.7 jsonrpclib==0.1.7

View file

@ -47,7 +47,8 @@ requires = [
'base58', 'base58',
'googlefinance', 'googlefinance',
'requests_futures', 'requests_futures',
'PyYAML' 'PyYAML',
'envparse'
] ]
console_scripts = [ console_scripts = [
@ -251,6 +252,7 @@ elif platform == WINDOWS:
'cx_Freeze', 'cx_Freeze',
'dns', 'dns',
'ecdsa', 'ecdsa',
'envparse',
'gmpy', 'gmpy',
'googlefinance', 'googlefinance',
'jsonrpc', 'jsonrpc',