import os.path
from pathlib import Path
import sys
import yaml
from flexiznam.errors import ConfigurationError
from flexiznam.config.default_config import DEFAULT_CONFIG
from getpass import getpass
def _find_file(file_name, config_folder=None):
"""Find a file by looking at various places
Only in config_folder (if provided)
Otherwise look:
- in the current directory
- then in the ~/.config folder
- then in the folder contain the file defining this function
- then in sys.path
"""
if config_folder is not None:
in_config_folder = Path(config_folder) / file_name
if in_config_folder.is_file():
return in_config_folder
raise ConfigurationError('Cannot find %s in %s' % (file_name, config_folder))
local = Path.cwd() / file_name
if local.is_file():
return local
config = Path(__file__).parent.absolute() / 'config' / file_name
home = Path.home() / '.flexiznam'
if home.is_dir() and (home / file_name).is_file():
return home / file_name
if config.is_file():
return config
for directory in sys.path:
fname = Path(directory) / file_name
if fname.is_file():
return fname
raise ConfigurationError('Cannot find %s' % file_name)
[docs]def load_param(param_folder=None, config_file='config.yml'):
"""Read parameter file from config folder"""
if param_folder is None:
param_file = _find_file(config_file)
else:
param_file = Path(param_folder) / config_file
with open(param_file, 'r') as yml_file:
prm = yaml.safe_load(yml_file)
return prm
[docs]def get_password(username, app, password_file=None):
"""Read the password yaml"""
if password_file is None:
password_file = _find_file('secret_password.yml')
with open(password_file, 'r') as yml_file:
pwd = yaml.safe_load(yml_file) or {}
try:
if app not in pwd:
raise IOError('No password for %s' % app)
pwd = pwd[app]
if username not in pwd:
raise IOError('No %s password for user %s' % (app, username))
return pwd[username]
except IOError:
return getpass(prompt=f'Enter {app} password: ')
[docs]def add_password(app, username, password, password_file=None):
"""Add a password to a new or existing password file"""
if password_file is None:
try:
password_file = _find_file('secret_password.yml')
except ConfigurationError:
home = Path.home() / '.flexiznam'
if not home.is_dir():
os.mkdir(home)
password_file = home / 'secret_password.yml'
if os.path.isfile(password_file):
with open(password_file, 'r') as yml_file:
pwd = yaml.safe_load(yml_file) or {} # use empty dict if load returns None
else:
pwd = {}
# create or copy the app field
pwd[app] = pwd.get(app, {})
pwd[app][username] = password
with open(password_file, 'w') as yml_file:
yaml.dump(pwd, yml_file)
return password_file
[docs]def update_config(param_file=None, config_folder=None, skip_checks=False, **kwargs):
"""Update the current configuration
You can give any keyword arguments. For nested levels, provide a dictionary (of
dictionaries). For instance:
update_config(project_ids=dict(my_project='its_id'))
will add the new project into the project_ids dictionary without removing existing
projects.
If you want to replace a nested field by a flat structure, use the skip_checks=True
flag
"""
if config_folder is not None:
full_param_path = Path(config_folder) / param_file
else:
full_param_path = param_file
create_config(config_folder=config_folder, config_file=param_file, overwrite=True,
template=full_param_path, skip_checks=skip_checks, **kwargs)
[docs]def create_config(overwrite=False, config_folder=None, template=None, skip_checks=False,
config_file='config.yml', **kwargs):
"""Create a config file based on a template
If no template is provided, use ./config/default_config.py to generate a new config
file
**kwargs elements are used to update/supplement info found in the template
"""
if template is not None:
if isinstance(template, dict):
cfg = template
else: # we don't have a preloaded config, must be path to a file
with open(template, 'r') as tpl_file:
cfg = yaml.safe_load(tpl_file)
else:
cfg = DEFAULT_CONFIG
cfg = _recursive_update(cfg, kwargs, skip_checks=skip_checks)
if config_folder is None:
config_folder = Path.home() / '.flexiznam'
if not config_folder.is_dir():
os.mkdir(config_folder)
else:
config_folder = Path(config_folder)
assert config_folder.is_dir()
target_file = config_folder / config_file
if (not overwrite) and os.path.isfile(target_file):
raise IOError('Config file %s already exists.' % target_file)
with open(target_file, 'w') as cfg_yml:
yaml.dump(cfg, cfg_yml)
def _recursive_update(source, new_values, skip_checks=False):
"""Update dict of dict recursively"""
for k, v in new_values.items():
if isinstance(v, dict):
source[k] = _recursive_update(source.get(k, {}), v)
else:
if not skip_checks:
assert not isinstance(source.get(k, None), dict)
source[k] = v
return source
try:
PARAMETERS = load_param()
# expanduser for file paths:
PARAMETERS['download_folder'] = Path(PARAMETERS['download_folder']).expanduser()
except ConfigurationError:
print('Could not load the parameters. Check your configuration file')
PARAMETERS = {}
__all__ = ['load_param', 'get_password', 'add_password', 'update_config', 'create_config',
'PARAMETERS']