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
|
||||
|
||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||
.. moduleauthor:: Fabio Manganiello <fabio@manganiello.tech>
|
||||
.. license: MIT
|
||||
"""
|
||||
|
||||
|
@ -11,6 +11,7 @@ import os
|
|||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from .bus import Bus
|
||||
from .bus.redis import RedisBus
|
||||
from .config import Config
|
||||
from .context import register_backends, register_plugins
|
||||
|
@ -33,20 +34,9 @@ log = logging.getLogger('platypush')
|
|||
class 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_redis_queue = 'platypush/bus'
|
||||
|
||||
pidfile = None
|
||||
|
||||
# backend_name => backend_obj map
|
||||
backends = None
|
||||
|
||||
|
@ -80,25 +70,16 @@ class Daemon:
|
|||
verbose -- Enable debug/verbose logging, overriding the stored configuration (default: False).
|
||||
"""
|
||||
|
||||
if pidfile:
|
||||
self.pidfile = pidfile
|
||||
with open(self.pidfile, 'w') as f:
|
||||
if pidfile:
|
||||
with open(pidfile, 'w') as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
self.bus: Optional[Bus] = None
|
||||
self.redis_queue = redis_queue or self._default_redis_queue
|
||||
self.config_file = config_file
|
||||
self._verbose = verbose
|
||||
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_stderr = no_capture_stderr
|
||||
|
@ -108,6 +89,23 @@ class Daemon:
|
|||
self.processed_requests = 0
|
||||
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
|
||||
def build_from_cmdline(cls, args):
|
||||
"""
|
||||
|
@ -122,7 +120,7 @@ class Daemon:
|
|||
dest='config',
|
||||
required=False,
|
||||
default=None,
|
||||
help=cls.config_file.__doc__,
|
||||
help='Custom location for the configuration file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
|
@ -207,7 +205,7 @@ class Daemon:
|
|||
try:
|
||||
msg.execute(n_tries=self.n_tries)
|
||||
except PermissionError:
|
||||
log.info('Dropped unauthorized request: {}'.format(msg))
|
||||
log.info('Dropped unauthorized request: %s', msg)
|
||||
|
||||
self.processed_requests += 1
|
||||
if (
|
||||
|
@ -255,7 +253,7 @@ class Daemon:
|
|||
sys.stderr = Logger(log.warning)
|
||||
|
||||
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
|
||||
self.backends = register_backends(bus=self.bus, global_scope=True)
|
||||
|
|
|
@ -58,72 +58,51 @@ class Config:
|
|||
)
|
||||
_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
|
||||
Config.init), you won't probably need to call the constructor directly
|
||||
Params:
|
||||
cfgfile -- Config file path (default: retrieve the first
|
||||
available location in _cfgfile_locations)
|
||||
|
||||
:param cfgfile: Config file path (default: retrieve the first available
|
||||
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:
|
||||
cfgfile = self._get_default_cfgfile()
|
||||
|
||||
if cfgfile is None:
|
||||
cfgfile = self._create_default_config()
|
||||
|
||||
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
|
||||
self._cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
|
||||
|
||||
def _init_logging(self):
|
||||
logging_config = {
|
||||
'level': logging.INFO,
|
||||
'stream': sys.stdout,
|
||||
|
@ -152,28 +131,65 @@ class 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:
|
||||
self._config['device_id'] = socket.gethostname()
|
||||
|
||||
def _init_environment(self):
|
||||
if 'environment' in self._config:
|
||||
for k, v in self._config['environment'].items():
|
||||
os.environ[k] = str(v)
|
||||
|
||||
self.backends = {}
|
||||
self.plugins = self._core_plugins
|
||||
self.event_hooks = {}
|
||||
self.procedures = {}
|
||||
self.constants = {}
|
||||
self.cronjobs = {}
|
||||
self.dashboards = {}
|
||||
self._plugin_manifests = {}
|
||||
self._backend_manifests = {}
|
||||
def _init_dirs(self):
|
||||
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)
|
||||
|
||||
self._init_manifests()
|
||||
self._init_constants()
|
||||
self._load_scripts()
|
||||
self._init_components()
|
||||
self._init_dashboards(self._config['dashboards_dir'])
|
||||
if 'scripts_dir' not in self._config:
|
||||
self._config['scripts_dir'] = os.path.join(
|
||||
os.path.dirname(self._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(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
|
||||
def _core_plugins(self) -> Dict[str, dict]:
|
||||
|
|
|
@ -11,7 +11,7 @@ import time
|
|||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
logger = logging.getLogger('platypush')
|
||||
_logger = logging.getLogger('platypush')
|
||||
|
||||
|
||||
class JSONAble(ABC):
|
||||
|
@ -88,31 +88,33 @@ class Message:
|
|||
try:
|
||||
return super().default(obj)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
_logger.warning(
|
||||
'Could not serialize object type %s: %s: %s', type(obj), e, obj
|
||||
)
|
||||
|
||||
def __init__(self, *_, timestamp=None, logging_level=logging.INFO, **__):
|
||||
self.timestamp = timestamp or time.time()
|
||||
self.logging_level = logging_level
|
||||
self._logger = _logger
|
||||
self._default_log_prefix = ''
|
||||
|
||||
def log(self, prefix=''):
|
||||
if self.logging_level is None:
|
||||
return # Skip logging
|
||||
|
||||
log_func = logger.info
|
||||
log_func = self._logger.info
|
||||
if self.logging_level == logging.DEBUG:
|
||||
log_func = logger.debug
|
||||
log_func = self._logger.debug
|
||||
elif self.logging_level == logging.WARNING:
|
||||
log_func = logger.warning
|
||||
log_func = self._logger.warning
|
||||
elif self.logging_level == logging.ERROR:
|
||||
log_func = logger.error
|
||||
log_func = self._logger.error
|
||||
elif self.logging_level == logging.FATAL:
|
||||
log_func = logger.fatal
|
||||
log_func = self._logger.fatal
|
||||
|
||||
if not prefix:
|
||||
prefix = f'Received {self.__class__.__name__}: '
|
||||
log_func(f'{prefix}{self}')
|
||||
prefix = self._default_log_prefix
|
||||
log_func('%s%s', prefix, self)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
|
@ -154,7 +156,7 @@ class Message:
|
|||
try:
|
||||
msg = json.loads(msg.strip())
|
||||
except (ValueError, TypeError):
|
||||
logger.warning('Invalid JSON message: %s', msg)
|
||||
_logger.warning('Invalid JSON message: %s', msg)
|
||||
|
||||
assert isinstance(msg, dict)
|
||||
|
||||
|
|
Loading…
Reference in a new issue