forked from platypush/platypush
Refactored Config.__init__
.
The constructor of the `Config` class had grown too big. It's much more manageable if split into multiple sub-constructor helpers.
This commit is contained in:
parent
0a3d6add83
commit
c846c61493
3 changed files with 122 additions and 106 deletions
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Platypush
|
Platypush
|
||||||
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
.. moduleauthor:: Fabio Manganiello <fabio@manganiello.tech>
|
||||||
.. license: MIT
|
.. license: MIT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from .bus import Bus
|
||||||
from .bus.redis import RedisBus
|
from .bus.redis import RedisBus
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .context import register_backends, register_plugins
|
from .context import register_backends, register_plugins
|
||||||
|
@ -33,20 +34,9 @@ log = logging.getLogger('platypush')
|
||||||
class Daemon:
|
class Daemon:
|
||||||
"""Main class for the Platypush daemon"""
|
"""Main class for the Platypush daemon"""
|
||||||
|
|
||||||
# Configuration file (default: either ~/.config/platypush/config.yaml or
|
|
||||||
# /etc/platypush/config.yaml
|
|
||||||
config_file = None
|
|
||||||
|
|
||||||
# Application bus. It's an internal queue where:
|
|
||||||
# - backends will post the messages they receive
|
|
||||||
# - plugins will post the responses they process
|
|
||||||
bus = None
|
|
||||||
|
|
||||||
# Default bus queue name
|
# Default bus queue name
|
||||||
_default_redis_queue = 'platypush/bus'
|
_default_redis_queue = 'platypush/bus'
|
||||||
|
|
||||||
pidfile = None
|
|
||||||
|
|
||||||
# backend_name => backend_obj map
|
# backend_name => backend_obj map
|
||||||
backends = None
|
backends = None
|
||||||
|
|
||||||
|
@ -80,25 +70,16 @@ class Daemon:
|
||||||
verbose -- Enable debug/verbose logging, overriding the stored configuration (default: False).
|
verbose -- Enable debug/verbose logging, overriding the stored configuration (default: False).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.pidfile = pidfile
|
||||||
if pidfile:
|
if pidfile:
|
||||||
self.pidfile = pidfile
|
with open(pidfile, 'w') as f:
|
||||||
with open(self.pidfile, 'w') as f:
|
|
||||||
f.write(str(os.getpid()))
|
f.write(str(os.getpid()))
|
||||||
|
|
||||||
|
self.bus: Optional[Bus] = None
|
||||||
self.redis_queue = redis_queue or self._default_redis_queue
|
self.redis_queue = redis_queue or self._default_redis_queue
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
|
self._verbose = verbose
|
||||||
Config.init(self.config_file)
|
Config.init(self.config_file)
|
||||||
logging_conf = Config.get('logging') or {}
|
|
||||||
if verbose:
|
|
||||||
logging_conf['level'] = logging.DEBUG
|
|
||||||
logging.basicConfig(**logging_conf)
|
|
||||||
|
|
||||||
redis_conf = Config.get('backend.redis') or {}
|
|
||||||
self.bus = RedisBus(
|
|
||||||
redis_queue=self.redis_queue,
|
|
||||||
on_message=self.on_message(),
|
|
||||||
**redis_conf.get('redis_args', {})
|
|
||||||
)
|
|
||||||
|
|
||||||
self.no_capture_stdout = no_capture_stdout
|
self.no_capture_stdout = no_capture_stdout
|
||||||
self.no_capture_stderr = no_capture_stderr
|
self.no_capture_stderr = no_capture_stderr
|
||||||
|
@ -108,6 +89,23 @@ class Daemon:
|
||||||
self.processed_requests = 0
|
self.processed_requests = 0
|
||||||
self.cron_scheduler = None
|
self.cron_scheduler = None
|
||||||
|
|
||||||
|
self._init_bus()
|
||||||
|
self._init_logging()
|
||||||
|
|
||||||
|
def _init_bus(self):
|
||||||
|
redis_conf = Config.get('backend.redis') or {}
|
||||||
|
self.bus = RedisBus(
|
||||||
|
redis_queue=self.redis_queue,
|
||||||
|
on_message=self.on_message(),
|
||||||
|
**redis_conf.get('redis_args', {})
|
||||||
|
)
|
||||||
|
|
||||||
|
def _init_logging(self):
|
||||||
|
logging_conf = Config.get('logging') or {}
|
||||||
|
if self._verbose:
|
||||||
|
logging_conf['level'] = logging.DEBUG
|
||||||
|
logging.basicConfig(**logging_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_from_cmdline(cls, args):
|
def build_from_cmdline(cls, args):
|
||||||
"""
|
"""
|
||||||
|
@ -122,7 +120,7 @@ class Daemon:
|
||||||
dest='config',
|
dest='config',
|
||||||
required=False,
|
required=False,
|
||||||
default=None,
|
default=None,
|
||||||
help=cls.config_file.__doc__,
|
help='Custom location for the configuration file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--version',
|
'--version',
|
||||||
|
@ -207,7 +205,7 @@ class Daemon:
|
||||||
try:
|
try:
|
||||||
msg.execute(n_tries=self.n_tries)
|
msg.execute(n_tries=self.n_tries)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
log.info('Dropped unauthorized request: {}'.format(msg))
|
log.info('Dropped unauthorized request: %s', msg)
|
||||||
|
|
||||||
self.processed_requests += 1
|
self.processed_requests += 1
|
||||||
if (
|
if (
|
||||||
|
@ -255,7 +253,7 @@ class Daemon:
|
||||||
sys.stderr = Logger(log.warning)
|
sys.stderr = Logger(log.warning)
|
||||||
|
|
||||||
set_thread_name('platypush')
|
set_thread_name('platypush')
|
||||||
log.info('---- Starting platypush v.{}'.format(__version__))
|
log.info('---- Starting platypush v.%s', __version__)
|
||||||
|
|
||||||
# Initialize the backends and link them to the bus
|
# Initialize the backends and link them to the bus
|
||||||
self.backends = register_backends(bus=self.bus, global_scope=True)
|
self.backends = register_backends(bus=self.bus, global_scope=True)
|
||||||
|
|
|
@ -58,72 +58,51 @@ class Config:
|
||||||
)
|
)
|
||||||
_included_files: Set[str] = set()
|
_included_files: Set[str] = set()
|
||||||
|
|
||||||
def __init__(self, cfgfile=None):
|
def __init__(self, cfgfile: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Constructor. Always use the class as a singleton (i.e. through
|
Constructor. Always use the class as a singleton (i.e. through
|
||||||
Config.init), you won't probably need to call the constructor directly
|
Config.init), you won't probably need to call the constructor directly
|
||||||
Params:
|
|
||||||
cfgfile -- Config file path (default: retrieve the first
|
:param cfgfile: Config file path (default: retrieve the first available
|
||||||
available location in _cfgfile_locations)
|
location in _cfgfile_locations).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.backends = {}
|
||||||
|
self.plugins = self._core_plugins
|
||||||
|
self.event_hooks = {}
|
||||||
|
self.procedures = {}
|
||||||
|
self.constants = {}
|
||||||
|
self.cronjobs = {}
|
||||||
|
self.dashboards = {}
|
||||||
|
self._plugin_manifests = {}
|
||||||
|
self._backend_manifests = {}
|
||||||
|
self._cfgfile = ''
|
||||||
|
|
||||||
|
self._init_cfgfile(cfgfile)
|
||||||
|
self._config = self._read_config_file(self._cfgfile)
|
||||||
|
|
||||||
|
self._init_secrets()
|
||||||
|
self._init_dirs()
|
||||||
|
self._init_db()
|
||||||
|
self._init_logging()
|
||||||
|
self._init_device_id()
|
||||||
|
self._init_environment()
|
||||||
|
self._init_manifests()
|
||||||
|
self._init_constants()
|
||||||
|
self._load_scripts()
|
||||||
|
self._init_components()
|
||||||
|
self._init_dashboards(self._config['dashboards_dir'])
|
||||||
|
|
||||||
|
def _init_cfgfile(self, cfgfile: Optional[str] = None):
|
||||||
if cfgfile is None:
|
if cfgfile is None:
|
||||||
cfgfile = self._get_default_cfgfile()
|
cfgfile = self._get_default_cfgfile()
|
||||||
|
|
||||||
if cfgfile is None:
|
if cfgfile is None:
|
||||||
cfgfile = self._create_default_config()
|
cfgfile = self._create_default_config()
|
||||||
|
|
||||||
cfgfile = 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)
|
|
||||||
|
|
||||||
if 'token' in self._config:
|
|
||||||
self._config['token_hash'] = get_hash(self._config['token'])
|
|
||||||
|
|
||||||
if 'workdir' not in self._config:
|
|
||||||
self._config['workdir'] = self._workdir_location
|
|
||||||
self._config['workdir'] = os.path.expanduser(self._config['workdir'])
|
|
||||||
os.makedirs(self._config['workdir'], exist_ok=True)
|
|
||||||
|
|
||||||
if 'scripts_dir' not in self._config:
|
|
||||||
self._config['scripts_dir'] = os.path.join(
|
|
||||||
os.path.dirname(cfgfile), 'scripts'
|
|
||||||
)
|
|
||||||
os.makedirs(self._config['scripts_dir'], mode=0o755, exist_ok=True)
|
|
||||||
|
|
||||||
if 'dashboards_dir' not in self._config:
|
|
||||||
self._config['dashboards_dir'] = os.path.join(
|
|
||||||
os.path.dirname(cfgfile), 'dashboards'
|
|
||||||
)
|
|
||||||
os.makedirs(self._config['dashboards_dir'], mode=0o755, exist_ok=True)
|
|
||||||
|
|
||||||
# Create a default (empty) __init__.py in the scripts folder
|
|
||||||
init_py = os.path.join(self._config['scripts_dir'], '__init__.py')
|
|
||||||
if not os.path.isfile(init_py):
|
|
||||||
with open(init_py, 'w') as f:
|
|
||||||
f.write('# Auto-generated __init__.py - do not remove\n')
|
|
||||||
|
|
||||||
# Include scripts_dir parent in sys.path so members can be imported in scripts
|
|
||||||
# through the `scripts` package
|
|
||||||
scripts_parent_dir = str(
|
|
||||||
pathlib.Path(self._config['scripts_dir']).absolute().parent
|
|
||||||
)
|
|
||||||
sys.path = [scripts_parent_dir] + sys.path
|
|
||||||
|
|
||||||
# Initialize the default db connection string
|
|
||||||
db_engine = self._config.get('main.db', '')
|
|
||||||
if db_engine:
|
|
||||||
if isinstance(db_engine, str):
|
|
||||||
db_engine = {
|
|
||||||
'engine': db_engine,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
db_engine = {
|
|
||||||
'engine': 'sqlite:///'
|
|
||||||
+ os.path.join(quote(self._config['workdir']), 'main.db')
|
|
||||||
}
|
|
||||||
|
|
||||||
self._config['db'] = db_engine
|
|
||||||
|
|
||||||
|
def _init_logging(self):
|
||||||
logging_config = {
|
logging_config = {
|
||||||
'level': logging.INFO,
|
'level': logging.INFO,
|
||||||
'stream': sys.stdout,
|
'stream': sys.stdout,
|
||||||
|
@ -152,28 +131,65 @@ class Config:
|
||||||
|
|
||||||
self._config['logging'] = logging_config
|
self._config['logging'] = logging_config
|
||||||
|
|
||||||
|
def _init_db(self):
|
||||||
|
# Initialize the default db connection string
|
||||||
|
db_engine = self._config.get('main.db', '')
|
||||||
|
if db_engine:
|
||||||
|
if isinstance(db_engine, str):
|
||||||
|
db_engine = {
|
||||||
|
'engine': db_engine,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
db_engine = {
|
||||||
|
'engine': 'sqlite:///'
|
||||||
|
+ os.path.join(quote(self._config['workdir']), 'main.db')
|
||||||
|
}
|
||||||
|
|
||||||
|
self._config['db'] = db_engine
|
||||||
|
|
||||||
|
def _init_device_id(self):
|
||||||
if 'device_id' not in self._config:
|
if 'device_id' not in self._config:
|
||||||
self._config['device_id'] = socket.gethostname()
|
self._config['device_id'] = socket.gethostname()
|
||||||
|
|
||||||
|
def _init_environment(self):
|
||||||
if 'environment' in self._config:
|
if 'environment' in self._config:
|
||||||
for k, v in self._config['environment'].items():
|
for k, v in self._config['environment'].items():
|
||||||
os.environ[k] = str(v)
|
os.environ[k] = str(v)
|
||||||
|
|
||||||
self.backends = {}
|
def _init_dirs(self):
|
||||||
self.plugins = self._core_plugins
|
if 'workdir' not in self._config:
|
||||||
self.event_hooks = {}
|
self._config['workdir'] = self._workdir_location
|
||||||
self.procedures = {}
|
self._config['workdir'] = os.path.expanduser(self._config['workdir'])
|
||||||
self.constants = {}
|
os.makedirs(self._config['workdir'], exist_ok=True)
|
||||||
self.cronjobs = {}
|
|
||||||
self.dashboards = {}
|
|
||||||
self._plugin_manifests = {}
|
|
||||||
self._backend_manifests = {}
|
|
||||||
|
|
||||||
self._init_manifests()
|
if 'scripts_dir' not in self._config:
|
||||||
self._init_constants()
|
self._config['scripts_dir'] = os.path.join(
|
||||||
self._load_scripts()
|
os.path.dirname(self._cfgfile), 'scripts'
|
||||||
self._init_components()
|
)
|
||||||
self._init_dashboards(self._config['dashboards_dir'])
|
os.makedirs(self._config['scripts_dir'], mode=0o755, exist_ok=True)
|
||||||
|
|
||||||
|
if 'dashboards_dir' not in self._config:
|
||||||
|
self._config['dashboards_dir'] = os.path.join(
|
||||||
|
os.path.dirname(self._cfgfile), 'dashboards'
|
||||||
|
)
|
||||||
|
os.makedirs(self._config['dashboards_dir'], mode=0o755, exist_ok=True)
|
||||||
|
|
||||||
|
# Create a default (empty) __init__.py in the scripts folder
|
||||||
|
init_py = os.path.join(self._config['scripts_dir'], '__init__.py')
|
||||||
|
if not os.path.isfile(init_py):
|
||||||
|
with open(init_py, 'w') as f:
|
||||||
|
f.write('# Auto-generated __init__.py - do not remove\n')
|
||||||
|
|
||||||
|
# Include scripts_dir parent in sys.path so members can be imported in scripts
|
||||||
|
# through the `scripts` package
|
||||||
|
scripts_parent_dir = str(
|
||||||
|
pathlib.Path(self._config['scripts_dir']).absolute().parent
|
||||||
|
)
|
||||||
|
sys.path = [scripts_parent_dir] + sys.path
|
||||||
|
|
||||||
|
def _init_secrets(self):
|
||||||
|
if 'token' in self._config:
|
||||||
|
self._config['token_hash'] = get_hash(self._config['token'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _core_plugins(self) -> Dict[str, dict]:
|
def _core_plugins(self) -> Dict[str, dict]:
|
||||||
|
|
|
@ -11,7 +11,7 @@ import time
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
logger = logging.getLogger('platypush')
|
_logger = logging.getLogger('platypush')
|
||||||
|
|
||||||
|
|
||||||
class JSONAble(ABC):
|
class JSONAble(ABC):
|
||||||
|
@ -88,31 +88,33 @@ class Message:
|
||||||
try:
|
try:
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
_logger.warning(
|
||||||
'Could not serialize object type %s: %s: %s', type(obj), e, obj
|
'Could not serialize object type %s: %s: %s', type(obj), e, obj
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *_, timestamp=None, logging_level=logging.INFO, **__):
|
def __init__(self, *_, timestamp=None, logging_level=logging.INFO, **__):
|
||||||
self.timestamp = timestamp or time.time()
|
self.timestamp = timestamp or time.time()
|
||||||
self.logging_level = logging_level
|
self.logging_level = logging_level
|
||||||
|
self._logger = _logger
|
||||||
|
self._default_log_prefix = ''
|
||||||
|
|
||||||
def log(self, prefix=''):
|
def log(self, prefix=''):
|
||||||
if self.logging_level is None:
|
if self.logging_level is None:
|
||||||
return # Skip logging
|
return # Skip logging
|
||||||
|
|
||||||
log_func = logger.info
|
log_func = self._logger.info
|
||||||
if self.logging_level == logging.DEBUG:
|
if self.logging_level == logging.DEBUG:
|
||||||
log_func = logger.debug
|
log_func = self._logger.debug
|
||||||
elif self.logging_level == logging.WARNING:
|
elif self.logging_level == logging.WARNING:
|
||||||
log_func = logger.warning
|
log_func = self._logger.warning
|
||||||
elif self.logging_level == logging.ERROR:
|
elif self.logging_level == logging.ERROR:
|
||||||
log_func = logger.error
|
log_func = self._logger.error
|
||||||
elif self.logging_level == logging.FATAL:
|
elif self.logging_level == logging.FATAL:
|
||||||
log_func = logger.fatal
|
log_func = self._logger.fatal
|
||||||
|
|
||||||
if not prefix:
|
if not prefix:
|
||||||
prefix = f'Received {self.__class__.__name__}: '
|
prefix = self._default_log_prefix
|
||||||
log_func(f'{prefix}{self}')
|
log_func('%s%s', prefix, self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
|
@ -154,7 +156,7 @@ class Message:
|
||||||
try:
|
try:
|
||||||
msg = json.loads(msg.strip())
|
msg = json.loads(msg.strip())
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
logger.warning('Invalid JSON message: %s', msg)
|
_logger.warning('Invalid JSON message: %s', msg)
|
||||||
|
|
||||||
assert isinstance(msg, dict)
|
assert isinstance(msg, dict)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue