forked from platypush/platypush
Generate a default config.yaml if none is present instead of failing
This commit is contained in:
parent
da73a5f1b9
commit
371fd7e46b
3 changed files with 124 additions and 57 deletions
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import glob
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
@ -6,19 +7,25 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from platypush.utils import get_hash, is_functional_procedure, is_functional_hook, is_functional_cron
|
from platypush.utils import (
|
||||||
|
get_hash,
|
||||||
|
is_functional_procedure,
|
||||||
|
is_functional_hook,
|
||||||
|
is_functional_cron,
|
||||||
|
)
|
||||||
|
|
||||||
""" Config singleton instance """
|
""" Config singleton instance """
|
||||||
_default_config_instance = None
|
_default_config_instance = None
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config:
|
||||||
"""
|
"""
|
||||||
Configuration base class
|
Configuration base class
|
||||||
Usage:
|
Usage:
|
||||||
|
@ -45,7 +52,9 @@ class Config(object):
|
||||||
'now': datetime.datetime.now,
|
'now': datetime.datetime.now,
|
||||||
}
|
}
|
||||||
|
|
||||||
_workdir_location = os.path.join(os.path.expanduser('~'), '.local', 'share', 'platypush')
|
_workdir_location = os.path.join(
|
||||||
|
os.path.expanduser('~'), '.local', 'share', 'platypush'
|
||||||
|
)
|
||||||
_included_files = set()
|
_included_files = set()
|
||||||
|
|
||||||
def __init__(self, cfgfile=None):
|
def __init__(self, cfgfile=None):
|
||||||
|
@ -61,14 +70,12 @@ class Config(object):
|
||||||
cfgfile = self._get_default_cfgfile()
|
cfgfile = self._get_default_cfgfile()
|
||||||
|
|
||||||
if cfgfile is None:
|
if cfgfile is None:
|
||||||
raise RuntimeError('No config file specified and nothing found in {}'
|
cfgfile = self._create_default_config()
|
||||||
.format(self._cfgfile_locations))
|
|
||||||
|
|
||||||
self._cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
|
self._cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
|
||||||
self._config = self._read_config_file(self._cfgfile)
|
self._config = self._read_config_file(self._cfgfile)
|
||||||
|
|
||||||
if 'token' in self._config:
|
if 'token' in self._config:
|
||||||
self._config['token'] = self._config['token']
|
|
||||||
self._config['token_hash'] = get_hash(self._config['token'])
|
self._config['token_hash'] = get_hash(self._config['token'])
|
||||||
|
|
||||||
if 'workdir' not in self._config:
|
if 'workdir' not in self._config:
|
||||||
|
@ -76,11 +83,15 @@ class Config(object):
|
||||||
os.makedirs(self._config['workdir'], exist_ok=True)
|
os.makedirs(self._config['workdir'], exist_ok=True)
|
||||||
|
|
||||||
if 'scripts_dir' not in self._config:
|
if 'scripts_dir' not in self._config:
|
||||||
self._config['scripts_dir'] = os.path.join(os.path.dirname(cfgfile), 'scripts')
|
self._config['scripts_dir'] = os.path.join(
|
||||||
|
os.path.dirname(cfgfile), 'scripts'
|
||||||
|
)
|
||||||
os.makedirs(self._config['scripts_dir'], mode=0o755, exist_ok=True)
|
os.makedirs(self._config['scripts_dir'], mode=0o755, exist_ok=True)
|
||||||
|
|
||||||
if 'dashboards_dir' not in self._config:
|
if 'dashboards_dir' not in self._config:
|
||||||
self._config['dashboards_dir'] = os.path.join(os.path.dirname(cfgfile), 'dashboards')
|
self._config['dashboards_dir'] = os.path.join(
|
||||||
|
os.path.dirname(cfgfile), 'dashboards'
|
||||||
|
)
|
||||||
os.makedirs(self._config['dashboards_dir'], mode=0o755, exist_ok=True)
|
os.makedirs(self._config['dashboards_dir'], mode=0o755, exist_ok=True)
|
||||||
|
|
||||||
init_py = os.path.join(self._config['scripts_dir'], '__init__.py')
|
init_py = os.path.join(self._config['scripts_dir'], '__init__.py')
|
||||||
|
@ -90,13 +101,20 @@ class Config(object):
|
||||||
|
|
||||||
# Include scripts_dir parent in sys.path so members can be imported in scripts
|
# Include scripts_dir parent in sys.path so members can be imported in scripts
|
||||||
# through the `scripts` package
|
# through the `scripts` package
|
||||||
scripts_parent_dir = str(pathlib.Path(self._config['scripts_dir']).absolute().parent)
|
scripts_parent_dir = str(
|
||||||
|
pathlib.Path(self._config['scripts_dir']).absolute().parent
|
||||||
|
)
|
||||||
sys.path = [scripts_parent_dir] + sys.path
|
sys.path = [scripts_parent_dir] + sys.path
|
||||||
|
|
||||||
self._config['db'] = self._config.get('main.db', {
|
self._config['db'] = self._config.get(
|
||||||
'engine': 'sqlite:///' + os.path.join(
|
'main.db',
|
||||||
os.path.expanduser('~'), '.local', 'share', 'platypush', 'main.db')
|
{
|
||||||
})
|
'engine': 'sqlite:///'
|
||||||
|
+ os.path.join(
|
||||||
|
os.path.expanduser('~'), '.local', 'share', 'platypush', 'main.db'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
logging_config = {
|
logging_config = {
|
||||||
'level': logging.INFO,
|
'level': logging.INFO,
|
||||||
|
@ -112,8 +130,11 @@ class Config(object):
|
||||||
try:
|
try:
|
||||||
os.makedirs(logdir, exist_ok=True)
|
os.makedirs(logdir, exist_ok=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Unable to create logs directory {}: {}'.format(
|
print(
|
||||||
logdir, str(e)))
|
'Unable to create logs directory {}: {}'.format(
|
||||||
|
logdir, str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
v = logfile
|
v = logfile
|
||||||
del logging_config['stream']
|
del logging_config['stream']
|
||||||
|
@ -150,9 +171,18 @@ class Config(object):
|
||||||
self._init_components()
|
self._init_components()
|
||||||
self._init_dashboards(self._config['dashboards_dir'])
|
self._init_dashboards(self._config['dashboards_dir'])
|
||||||
|
|
||||||
|
def _create_default_config(self):
|
||||||
|
cfg_mod_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
cfgfile = self._cfgfile_locations[0]
|
||||||
|
cfgdir = pathlib.Path(cfgfile).parent
|
||||||
|
cfgdir.mkdir(parents=True, exist_ok=True)
|
||||||
|
for cfgfile in glob.glob(os.path.join(cfg_mod_dir, 'config*.yaml')):
|
||||||
|
shutil.copy(cfgfile, str(cfgdir))
|
||||||
|
|
||||||
|
return cfgfile
|
||||||
|
|
||||||
def _read_config_file(self, cfgfile):
|
def _read_config_file(self, cfgfile):
|
||||||
cfgfile_dir = os.path.dirname(os.path.abspath(
|
cfgfile_dir = os.path.dirname(os.path.abspath(os.path.expanduser(cfgfile)))
|
||||||
os.path.expanduser(cfgfile)))
|
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
|
@ -164,9 +194,11 @@ class Config(object):
|
||||||
|
|
||||||
for section in file_config:
|
for section in file_config:
|
||||||
if section == 'include':
|
if section == 'include':
|
||||||
include_files = file_config[section] \
|
include_files = (
|
||||||
if isinstance(file_config[section], list) \
|
file_config[section]
|
||||||
|
if isinstance(file_config[section], list)
|
||||||
else [file_config[section]]
|
else [file_config[section]]
|
||||||
|
)
|
||||||
|
|
||||||
for include_file in include_files:
|
for include_file in include_files:
|
||||||
if not os.path.isabs(include_file):
|
if not os.path.isabs(include_file):
|
||||||
|
@ -178,9 +210,13 @@ class Config(object):
|
||||||
config[incl_section] = included_config[incl_section]
|
config[incl_section] = included_config[incl_section]
|
||||||
elif section == 'scripts_dir':
|
elif section == 'scripts_dir':
|
||||||
assert isinstance(file_config[section], str)
|
assert isinstance(file_config[section], str)
|
||||||
config['scripts_dir'] = os.path.abspath(os.path.expanduser(file_config[section]))
|
config['scripts_dir'] = os.path.abspath(
|
||||||
elif 'disabled' not in file_config[section] \
|
os.path.expanduser(file_config[section])
|
||||||
or file_config[section]['disabled'] is False:
|
)
|
||||||
|
elif (
|
||||||
|
'disabled' not in file_config[section]
|
||||||
|
or file_config[section]['disabled'] is False
|
||||||
|
):
|
||||||
config[section] = file_config[section]
|
config[section] = file_config[section]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
@ -189,27 +225,37 @@ class Config(object):
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(modname)
|
module = importlib.import_module(modname)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Unhandled exception while importing module {}: {}'.format(modname, str(e)))
|
print(
|
||||||
|
'Unhandled exception while importing module {}: {}'.format(
|
||||||
|
modname, str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
prefix = modname + '.' if prefix is None else prefix
|
prefix = modname + '.' if prefix is None else prefix
|
||||||
self.procedures.update(**{
|
self.procedures.update(
|
||||||
prefix + name: obj
|
**{
|
||||||
for name, obj in inspect.getmembers(module)
|
prefix + name: obj
|
||||||
if is_functional_procedure(obj)
|
for name, obj in inspect.getmembers(module)
|
||||||
})
|
if is_functional_procedure(obj)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.event_hooks.update(**{
|
self.event_hooks.update(
|
||||||
prefix + name: obj
|
**{
|
||||||
for name, obj in inspect.getmembers(module)
|
prefix + name: obj
|
||||||
if is_functional_hook(obj)
|
for name, obj in inspect.getmembers(module)
|
||||||
})
|
if is_functional_hook(obj)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.cronjobs.update(**{
|
self.cronjobs.update(
|
||||||
prefix + name: obj
|
**{
|
||||||
for name, obj in inspect.getmembers(module)
|
prefix + name: obj
|
||||||
if is_functional_cron(obj)
|
for name, obj in inspect.getmembers(module)
|
||||||
})
|
if is_functional_cron(obj)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def _load_scripts(self):
|
def _load_scripts(self):
|
||||||
scripts_dir = self._config['scripts_dir']
|
scripts_dir = self._config['scripts_dir']
|
||||||
|
@ -218,14 +264,19 @@ class Config(object):
|
||||||
scripts_modname = os.path.basename(scripts_dir)
|
scripts_modname = os.path.basename(scripts_dir)
|
||||||
self._load_module(scripts_modname, prefix='')
|
self._load_module(scripts_modname, prefix='')
|
||||||
|
|
||||||
for _, modname, _ in pkgutil.walk_packages(path=[scripts_dir], onerror=lambda x: None):
|
for _, modname, _ in pkgutil.walk_packages(
|
||||||
|
path=[scripts_dir], onerror=lambda _: None
|
||||||
|
):
|
||||||
self._load_module(modname)
|
self._load_module(modname)
|
||||||
|
|
||||||
sys.path = sys_path
|
sys.path = sys_path
|
||||||
|
|
||||||
def _init_components(self):
|
def _init_components(self):
|
||||||
for key in self._config.keys():
|
for key in self._config.keys():
|
||||||
if key.startswith('backend.') and '.'.join(key.split('.')[1:]) in self._backend_manifests:
|
if (
|
||||||
|
key.startswith('backend.')
|
||||||
|
and '.'.join(key.split('.')[1:]) in self._backend_manifests
|
||||||
|
):
|
||||||
backend_name = '.'.join(key.split('.')[1:])
|
backend_name = '.'.join(key.split('.')[1:])
|
||||||
self.backends[backend_name] = self._config[key]
|
self.backends[backend_name] = self._config[key]
|
||||||
elif key.startswith('event.hook.'):
|
elif key.startswith('event.hook.'):
|
||||||
|
@ -236,7 +287,7 @@ class Config(object):
|
||||||
self.cronjobs[cron_name] = self._config[key]
|
self.cronjobs[cron_name] = self._config[key]
|
||||||
elif key.startswith('procedure.'):
|
elif key.startswith('procedure.'):
|
||||||
tokens = key.split('.')
|
tokens = key.split('.')
|
||||||
_async = True if len(tokens) > 2 and tokens[1] == 'async' else False
|
_async = bool(len(tokens) > 2 and tokens[1] == 'async')
|
||||||
procedure_name = '.'.join(tokens[2:] if len(tokens) > 2 else tokens[1:])
|
procedure_name = '.'.join(tokens[2:] if len(tokens) > 2 else tokens[1:])
|
||||||
args = []
|
args = []
|
||||||
m = re.match(r'^([^(]+)\(([^)]+)\)\s*', procedure_name)
|
m = re.match(r'^([^(]+)\(([^)]+)\)\s*', procedure_name)
|
||||||
|
@ -265,7 +316,11 @@ class Config(object):
|
||||||
self._init_manifests(plugins_dir)
|
self._init_manifests(plugins_dir)
|
||||||
self._init_manifests(backends_dir)
|
self._init_manifests(backends_dir)
|
||||||
else:
|
else:
|
||||||
manifests_map = self._plugin_manifests if base_dir.endswith('plugins') else self._backend_manifests
|
manifests_map = (
|
||||||
|
self._plugin_manifests
|
||||||
|
if base_dir.endswith('plugins')
|
||||||
|
else self._backend_manifests
|
||||||
|
)
|
||||||
for mf in pathlib.Path(base_dir).rglob('manifest.yaml'):
|
for mf in pathlib.Path(base_dir).rglob('manifest.yaml'):
|
||||||
with open(mf, 'r') as f:
|
with open(mf, 'r') as f:
|
||||||
manifest = yaml.safe_load(f)['manifest']
|
manifest = yaml.safe_load(f)['manifest']
|
||||||
|
@ -279,12 +334,11 @@ class Config(object):
|
||||||
for (key, value) in self._default_constants.items():
|
for (key, value) in self._default_constants.items():
|
||||||
self.constants[key] = value
|
self.constants[key] = value
|
||||||
|
|
||||||
@staticmethod
|
def _get_dashboard(
|
||||||
def get_dashboard(name: str, dashboards_dir: Optional[str] = None) -> Optional[str]:
|
self, name: str, dashboards_dir: Optional[str] = None
|
||||||
global _default_config_instance
|
) -> Optional[str]:
|
||||||
|
dashboards_dir = dashboards_dir or self._config['dashboards_dir']
|
||||||
# noinspection PyProtectedMember,PyProtectedMember,PyUnresolvedReferences
|
assert dashboards_dir
|
||||||
dashboards_dir = dashboards_dir or _default_config_instance._config['dashboards_dir']
|
|
||||||
abspath = os.path.join(dashboards_dir, name + '.xml')
|
abspath = os.path.join(dashboards_dir, name + '.xml')
|
||||||
if not os.path.isfile(abspath):
|
if not os.path.isfile(abspath):
|
||||||
return
|
return
|
||||||
|
@ -292,24 +346,37 @@ class Config(object):
|
||||||
with open(abspath, 'r') as fp:
|
with open(abspath, 'r') as fp:
|
||||||
return fp.read()
|
return fp.read()
|
||||||
|
|
||||||
@classmethod
|
def _get_dashboards(self, dashboards_dir: Optional[str] = None) -> dict:
|
||||||
def get_dashboards(cls, dashboards_dir: Optional[str] = None) -> dict:
|
|
||||||
global _default_config_instance
|
|
||||||
dashboards = {}
|
dashboards = {}
|
||||||
# noinspection PyProtectedMember,PyProtectedMember,PyUnresolvedReferences
|
dashboards_dir = dashboards_dir or self._config['dashboards_dir']
|
||||||
dashboards_dir = dashboards_dir or _default_config_instance._config['dashboards_dir']
|
assert dashboards_dir
|
||||||
|
|
||||||
for f in os.listdir(dashboards_dir):
|
for f in os.listdir(dashboards_dir):
|
||||||
abspath = os.path.join(dashboards_dir, f)
|
abspath = os.path.join(dashboards_dir, f)
|
||||||
if not os.path.isfile(abspath) or not abspath.endswith('.xml'):
|
if not os.path.isfile(abspath) or not abspath.endswith('.xml'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = f.split('.xml')[0]
|
name = f.split('.xml')[0]
|
||||||
dashboards[name] = cls.get_dashboard(name, dashboards_dir)
|
dashboards[name] = self._get_dashboard(name, dashboards_dir)
|
||||||
|
|
||||||
return dashboards
|
return dashboards
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dashboard(name: str, dashboards_dir: Optional[str] = None) -> Optional[str]:
|
||||||
|
global _default_config_instance
|
||||||
|
if _default_config_instance is None:
|
||||||
|
_default_config_instance = Config()
|
||||||
|
return _default_config_instance._get_dashboard(name, dashboards_dir)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dashboards(cls, dashboards_dir: Optional[str] = None) -> dict:
|
||||||
|
global _default_config_instance
|
||||||
|
if _default_config_instance is None:
|
||||||
|
_default_config_instance = Config()
|
||||||
|
return _default_config_instance._get_dashboards(dashboards_dir)
|
||||||
|
|
||||||
def _init_dashboards(self, dashboards_dir: str):
|
def _init_dashboards(self, dashboards_dir: str):
|
||||||
self.dashboards = self.get_dashboards(dashboards_dir)
|
self.dashboards = self._get_dashboards(dashboards_dir)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_backends():
|
def get_backends():
|
||||||
|
@ -400,4 +467,5 @@ class Config(object):
|
||||||
|
|
||||||
return _default_config_instance._config
|
return _default_config_instance._config
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -3,5 +3,4 @@
|
||||||
# instead
|
# instead
|
||||||
|
|
||||||
backend.http:
|
backend.http:
|
||||||
port: 8008
|
enabled: True
|
||||||
websocket_port: 8009
|
|
||||||
|
|
Loading…
Reference in a new issue