]> Frank Brehm's Git Trees - pixelpark/admin-tools.git/commitdiff
Adding classes for configured applications
authorFrank Brehm <frank.brehm@pixelpark.com>
Thu, 16 Mar 2017 14:40:42 +0000 (15:40 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Thu, 16 Mar 2017 14:40:42 +0000 (15:40 +0100)
mk-home [new file with mode: 0755]
pp_lib/cfg_app.py [new file with mode: 0644]
pp_lib/rec_dict.py [new file with mode: 0644]

diff --git a/mk-home b/mk-home
new file mode 100755 (executable)
index 0000000..370973f
--- /dev/null
+++ b/mk-home
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+# Standard modules
+import sys
+import os
+import logging
+import locale
+
+# own modules:
+cur_dir = os.getcwd()
+base_dir = cur_dir
+
+if sys.argv[0] != '' and sys.argv[0] != '-c':
+    cur_dir = os.path.dirname(sys.argv[0])
+if os.path.exists(os.path.join(cur_dir, 'pp_lib')):
+    sys.path.insert(0, os.path.abspath(cur_dir))
+
+from pp_lib.common import pp
+
+from pp_lib.cfg_app import PpConfigApplication
+
+log = logging.getLogger(__name__)
+
+__author__ = 'Frank Brehm <frank.brehm@pixelpark.com>'
+__copyright__ = '(C) 2017 by Frank Brehm, Pixelpark GmbH, Berlin'
+
+appname = os.path.basename(sys.argv[0])
+
+locale.setlocale(locale.LC_ALL, '')
+
+app = PpConfigApplication(appname=appname, cfg_dir='pixelpark')
+#app.initialized = True
+
+if app.verbose > 2:
+    print("{c}-Object:\n{a}".format(c=app.__class__.__name__, a=app))
+
+#app()
+
+sys.exit(0)
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
+
+# vim: ts=4
diff --git a/pp_lib/cfg_app.py b/pp_lib/cfg_app.py
new file mode 100644 (file)
index 0000000..658ea73
--- /dev/null
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2017 by Frank Brehm, Berlin
+@summary: The module for the application object.
+"""
+from __future__ import absolute_import
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+import traceback
+import textwrap
+import datetime
+
+# Third party modules
+import six
+
+from six import StringIO
+from six.moves import configparser
+
+from configparser import Error as ConfigParseError
+
+import argparse
+
+# Own modules
+from .global_version import __version__ as __global_version__
+
+from .errors import FunctionNotImplementedError, PpAppError
+
+from .common import pp, terminal_can_colors, to_bytes
+
+from .rec_dict import RecursiveDictionary
+
+from .app import PpApplication
+
+__version__ = '0.2.1'
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class PpCfgAppError(PpAppError):
+    """Base error class for all exceptions happened during
+    execution this configured application"""
+
+    pass
+
+
+# =============================================================================
+class PpConfigApplication(PpApplication):
+    """
+    Class for configured application objects.
+    """
+
+    # -------------------------------------------------------------------------
+    def __init__(
+        self, appname=None, verbose=0, version=__version__, base_dir=None,
+            initialized=None, usage=None, description=None,
+            argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None,
+            cfg_dir=None, cfg_stem=None, cfg_encoding='utf-8', need_config_file=False):
+
+        self._cfg_encoding = cfg_encoding
+        self._need_config_file = bool(need_config_file)
+
+        self.cfg = RecursiveDictionary()
+
+        self._cfg_dir = None
+        self._cfg_stem = None
+        self.cfg_files = []
+
+        super(PpConfigApplication, self).__init__(
+            appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+            initialized=False, usage=usage, description=description,
+            argparse_epilog=argparse_epilog, argparse_prefix_chars=argparse_prefix_chars,
+            env_prefix=env_prefix,
+        )
+
+        if cfg_dir is None:
+            self._cfg_dir = self.appname
+        else:
+            d = str(cfg_dir).strip()
+            if d == '':
+                self._cfg_dir = None
+            else:
+                self._cfg_dir = d
+
+        if cfg_stem:
+            s = str(cfg_stem).strip()
+            if not s:
+                msg = "Invalid configuration stem {!r} given.".format(cfg_stem)
+                raise PbCfgAppError(msg)
+            self._cfg_stem = s
+        else:
+            self._cfg_stem = self.appname
+
+        self.init_cfgfiles()
+
+        enc = getattr(self.args, 'cfg_encoding', None)
+        if enc:
+            enc = enc.lower()
+            if enc != self.cfg_encoding:
+                self._cfg_encoding = enc
+
+        self.post_init()
+        self._read_config()
+
+    # -----------------------------------------------------------
+    @property
+    def need_config_file(self):
+        """
+        hide command line parameter --default-config and
+        don't execute generation of default config
+        """
+        return getattr(self, '_need_config_file', False)
+
+    # -----------------------------------------------------------
+    @property
+    def cfg_encoding(self):
+        """The encoding character set of the configuration files."""
+        return self._cfg_encoding
+
+    # -----------------------------------------------------------
+    @property
+    def cfg_dir(self):
+        """The directory containing the configuration files."""
+        return self._cfg_dir
+
+    # -----------------------------------------------------------
+    @property
+    def cfg_stem(self):
+        """The basename of the configuration file without any file extension."""
+        return self._cfg_stem
+
+    # -------------------------------------------------------------------------
+    def as_dict(self, short=True):
+        """
+        Transforms the elements of the object into a dict
+
+        @param short: don't include local properties in resulting dict.
+        @type short: bool
+
+        @return: structure as dict
+        @rtype:  dict
+        """
+
+        res = super(PpConfigApplication, self).as_dict(short=short)
+        res['need_config_file'] = self.need_config_file
+        res['cfg_encoding'] = self.cfg_encoding
+        res['cfg_dir'] = self.cfg_dir
+        res['cfg_stem'] = self.cfg_stem
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def init_arg_parser(self):
+        """
+        Method to initiate the argument parser.
+
+        This method should be explicitely called by all init_arg_parser()
+        methods in descendant classes.
+        """
+        self.arg_parser.add_argument(
+            "-C", "--cfgfile", "--cfg-file", "--config",
+            metavar="FILE",
+            dest="cfg_file",
+            help="Configuration file to use additional to the standard configuration files.",
+        )
+
+        self.arg_parser.add_argument(
+            "--cfg-encoding",
+            metavar="ENCODING",
+            dest="cfg_encoding",
+            default=self.cfg_encoding,
+            help=("The encoding character set of the configuration files "
+                "(default: %(default)r)."),
+        )
+
+    # -------------------------------------------------------------------------
+    def init_cfgfiles(self):
+        """Method to generate the self.cfg_files list."""
+
+        self.cfg_files = []
+        cfg_basename = '{}.ini'.format(self.cfg_stem)
+
+        # add /etc/app/app.ini or $VIRTUAL_ENV/etc/app/app.ini
+        etc_dir = os.sep + 'etc'
+        if 'VIRTUAL_ENV' in os.environ:
+            etc_dir = os.path.join(os.environ['VIRTUAL_ENV'], 'etc')
+        syscfg_fn = None
+        if self.cfg_dir:
+            syscfg_fn = os.path.join(etc_dir, self.cfg_dir, cfg_basename)
+        else:
+            syscfg_fn = os.path.join(etc_dir, cfg_basename)
+        self.cfg_files.append(syscfg_fn)
+
+        # add <WORKDIR>/etc/app.ini
+        mod_dir = os.path.dirname(__file__)
+        work_dir = os.path.abspath(os.path.join(mod_dir, '..'))
+        work_etc_dir = os.path.join(work_dir, 'etc')
+        if self.verbose > 1:
+            LOG.debug("Searching for {!r} ...".format(work_etc_dir))
+        self.cfg_files.append(os.path.join(work_etc_dir, cfg_basename))
+
+        # add $HOME/.config/app.ini
+        usercfg_fn = None
+        user_cfg_dir = os.path.expanduser('~/.config')
+        if user_cfg_dir:
+            if self.cfg_dir:
+                user_cfg_dir = os.path.join(user_cfg_dir, self.cfg_dir)
+            if self.verbose > 1:
+                LOG.debug("user_cfg_dir: {!r}".format(user_cfg_dir))
+            usercfg_fn = os.path.join(user_cfg_dir, cfg_basename)
+            self.cfg_files.append(usercfg_fn)
+
+        # add a configfile given on command line with --cfg-file
+        cmdline_cfg = getattr(self.args, 'cfg_file', None)
+        if cmdline_cfg:
+            self.cfg_files.append(cmdline_cfg)
+
+    # -------------------------------------------------------------------------
+    def _read_config(self):
+
+        if self.verbose > 0:
+            LOG.debug("Reading config files with character set {!r} ...".format(
+                self.cfg_encoding))
+
+        open_opts = {}
+        if six.PY3 and self.cfg_encoding:
+            open_opts['encoding'] = self.cfg_encoding
+            open_opts['errors'] = 'surrogateescape'
+
+        found_cfg_files = False
+        for cfg_file in self.cfg_files:
+            if self.verbose > 2:
+                LOG.debug("Searching for {!r} ...".format(cfg_file))
+            if not os.path.isfile(cfg_file):
+                if self.verbose > 3:
+                    LOG.debug("Config file {!r} not found.".format(cfg_file))
+                continue
+            if self.verbose > 1:
+                LOG.debug("Reading {!r} ...".format(cfg_file))
+            found_cfg_files = True
+
+            config = configparser.ConfigParser()
+            try:
+                with open(cfg_file, 'r', **open_opts) as fh:
+                    stream = StringIO("[default]\n" + fh.read())
+                    if six.PY2:
+                        config.readfp(stram)
+                    else:
+                        config.read_file(stram)
+            except ConfigParseError as e:
+                msg = "Wrong configuration in {!r} found: ".format(cfg_file)
+                msg += str(e)
+                self.handle_error(msg, "Configuration error")
+                continue
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
diff --git a/pp_lib/rec_dict.py b/pp_lib/rec_dict.py
new file mode 100644 (file)
index 0000000..4060a89
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@summary: The module provides an object class with a dict, which can
+          be updated in a recursive way.
+          It is originated by Jannis Andrija Schnitzer::
+            https://gist.github.com/114831
+"""
+
+# Standard modules
+# import sys
+# import os
+import logging
+
+__author__ = 'jannis@itisme.org (Jannis Andrija Schnitzer)'
+__copyright__ = '(c) 2009 Jannis Andrija Schnitzer'
+__contact__ = 'jannis@itisme.org'
+__version__ = '0.2.1'
+__license__ = 'GPL3'
+
+log = logging.getLogger(__name__)
+
+
+# =============================================================================
+class RecursiveDictionary(dict):
+    """RecursiveDictionary provides the methods rec_update and iter_rec_update
+    that can be used to update member dictionaries rather than overwriting
+    them."""
+
+    # -------------------------------------------------------------------------
+    def rec_update(self, other, **third):
+        """Recursively update the dictionary with the contents of other and
+        third like dict.update() does - but don't overwrite sub-dictionaries.
+        Example:
+        >>> d = RecursiveDictionary({'foo': {'bar': 42}})
+        >>> d.rec_update({'foo': {'baz': 36}})
+        >>> d
+        {'foo': {'baz': 36, 'bar': 42}}
+        """
+
+        try:
+            iterator = iter(other.items())
+        except AttributeError:
+            iterator = other
+
+        self.iter_rec_update(iterator)
+        self.iter_rec_update(iter(third.items()))
+
+    # -------------------------------------------------------------------------
+    def iter_rec_update(self, iterator):
+        for (key, value) in iterator:
+            if key in self and \
+                    isinstance(self[key], dict) and isinstance(value, dict):
+                self[key] = RecursiveDictionary(self[key])
+                self[key].rec_update(value)
+            else:
+                self[key] = value
+
+    # -------------------------------------------------------------------------
+    def __repr__(self):
+        return super(self.__class__, self).__repr__()
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4