]> Frank Brehm's Git Trees - pixelpark/admin-tools.git/commitdiff
Adding some python modules
authorFrank Brehm <frank.brehm@pixelpark.com>
Mon, 13 Mar 2017 12:04:36 +0000 (13:04 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Mon, 13 Mar 2017 12:04:36 +0000 (13:04 +0100)
pp_lib/__init__.py [new file with mode: 0644]
pp_lib/colored.py [new file with mode: 0644]
pp_lib/common.py [new file with mode: 0644]
pp_lib/errors.py [new file with mode: 0644]
pp_lib/mailaddress.py [new file with mode: 0644]

diff --git a/pp_lib/__init__.py b/pp_lib/__init__.py
new file mode 100644 (file)
index 0000000..3934b42
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/env python3
+# -*- coding: utf-8 -*-
+
+__version__ = '0.1.1'
+
+# vim: ts=4 et list
diff --git a/pp_lib/colored.py b/pp_lib/colored.py
new file mode 100644 (file)
index 0000000..7e223a7
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@summary: additional logging formatter for colored output via console
+"""
+
+# Standard modules
+import logging
+# import os.path
+# import sys
+import copy
+
+# Third party modules
+
+# Own modules
+
+# import pb_provisioning.common
+
+# from pb_provisioning.common import to_unicode_or_bust, to_utf8_or_bust
+
+__version__ = '0.1.3'
+
+# =============================================================================
+# Color coding module variables and helper functions
+
+COLOR_CODE = {
+    'ENDC':              0,  # RESET COLOR
+    'BOLD':              1,
+    'UNDERLINE':         4,
+    'BLINK':             5,
+    'INVERT':            7,
+    'CONCEALD':          8,
+    'STRIKE':            9,
+    'GREY30':           90,
+    'GREY40':            2,
+    'GREY65':           37,
+    'GREY70':           97,
+    'GREY20_BG':        40,
+    'GREY33_BG':       100,
+    'GREY80_BG':        47,
+    'GREY93_BG':       107,
+    'DARK_RED':         31,
+    'RED':              91,
+    'RED_BG':           41,
+    'LIGHT_RED_BG':    101,
+    'DARK_YELLOW':      33,
+    'YELLOW':           93,
+    'YELLOW_BG':        43,
+    'LIGHT_YELLOW_BG': 103,
+    'DARK_BLUE':        34,
+    'BLUE':             94,
+    'BLUE_BG':          44,
+    'LIGHT_BLUE_BG':   104,
+    'DARK_MAGENTA':     35,
+    'PURPLE':           95,
+    'MAGENTA_BG':       45,
+    'LIGHT_PURPLE_BG': 105,
+    'DARK_CYAN':        36,
+    'AUQA':             96,
+    'AQUA':             96,
+    'CYAN_BG':          46,
+    'LIGHT_AUQA_BG':   106,
+    'LIGHT_AQUA_BG':   106,
+    'DARK_GREEN':       32,
+    'GREEN':            92,
+    'GREEN_BG':         42,
+    'LIGHT_GREEN_BG':  102,
+    'BLACK':            30,
+}
+
+
+# -----------------------------------------------------------------------------
+def termcode(num):
+    """
+    Output of an ANSII terminal code.
+    """
+
+    return('\033[%sm' % (num))
+
+
+# -----------------------------------------------------------------------------
+def colorstr(message, color):
+    """
+    Wrapper function to colorize the message.
+
+    @param message: The message to colorize
+    @type message: str
+    @param color: The color to use, must be one of the keys of COLOR_CODE
+    @type color: str
+
+    @return: the colorized message
+    @rtype: str
+
+    """
+
+    tcode = ''
+    if isinstance(color, (list, tuple)):
+        for clr in color:
+            tcode += termcode(COLOR_CODE[clr])
+    else:
+        tcode = termcode(COLOR_CODE[color])
+
+    return tcode + message + termcode(COLOR_CODE['ENDC'])
+
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class ColoredFormatter(logging.Formatter):
+    """
+    A variant of code found at:
+    http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored
+    """
+
+    LEVEL_COLOR = {
+        'DEBUG':    None,
+        'INFO':     'GREEN',
+        'WARNING':  'YELLOW',
+        'ERROR':    ('BOLD', 'RED'),
+        'CRITICAL': 'RED_BG',
+    }
+
+    # -------------------------------------------------------------------------
+    def __init__(self, fmt=None, datefmt=None):
+        """
+        Initialize the formatter with specified format strings.
+
+        Initialize the formatter either with the specified format string, or a
+        default. Allow for specialized date formatting with the optional
+        datefmt argument (if omitted, you get the ISO8601 format).
+        """
+
+        logging.Formatter.__init__(self, fmt, datefmt)
+
+    # -----------------------------------------------------------
+    @property
+    def color_debug(self):
+        """The color used to output debug messages."""
+        return self.LEVEL_COLOR['DEBUG']
+
+    @color_debug.setter
+    def color_debug(self, value):
+        self.LEVEL_COLOR['DEBUG'] = value
+
+    # -----------------------------------------------------------
+    @property
+    def color_info(self):
+        """The color used to output info messages."""
+        return self.LEVEL_COLOR['INFO']
+
+    @color_info.setter
+    def color_info(self, value):
+        self.LEVEL_COLOR['INFO'] = value
+
+    # -----------------------------------------------------------
+    @property
+    def color_warning(self):
+        """The color used to output warning messages."""
+        return self.LEVEL_COLOR['WARNING']
+
+    @color_warning.setter
+    def color_warning(self, value):
+        self.LEVEL_COLOR['WARNING'] = value
+
+    # -----------------------------------------------------------
+    @property
+    def color_error(self):
+        """The color used to output error messages."""
+        return self.LEVEL_COLOR['ERROR']
+
+    @color_error.setter
+    def color_error(self, value):
+        self.LEVEL_COLOR['ERROR'] = value
+
+    # -----------------------------------------------------------
+    @property
+    def color_critical(self):
+        """The color used to output critical messages."""
+        return self.LEVEL_COLOR['CRITICAL']
+
+    @color_critical.setter
+    def color_critical(self, value):
+        self.LEVEL_COLOR['CRITICAL'] = value
+
+    # -------------------------------------------------------------------------
+    def format(self, record):
+        """
+        Format the specified record as text.
+        """
+
+        record = copy.copy(record)
+        levelname = record.levelname
+
+        if levelname in self.LEVEL_COLOR:
+
+            record.name = colorstr(record.name, 'BOLD')
+            record.filename = colorstr(record.filename, 'BOLD')
+            record.module = colorstr(record.module, 'BOLD')
+            record.funcName = colorstr(record.funcName, 'BOLD')
+            record.pathname = colorstr(record.pathname, 'BOLD')
+            record.processName = colorstr(record.processName, 'BOLD')
+            record.threadName = colorstr(record.threadName, 'BOLD')
+
+            if self.LEVEL_COLOR[levelname] is not None:
+                record.levelname = colorstr(
+                    levelname, self.LEVEL_COLOR[levelname])
+                record.msg = colorstr(record.msg, self.LEVEL_COLOR[levelname])
+
+        return logging.Formatter.format(self, record)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
diff --git a/pp_lib/common.py b/pp_lib/common.py
new file mode 100644 (file)
index 0000000..a4180e3
--- /dev/null
@@ -0,0 +1,172 @@
+#!/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 common used functions.
+"""
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+import pprint
+import platform
+
+# Third party modules
+import six
+
+# Own modules
+
+__version__ = '0.2.1'
+
+log = logging.getLogger(__name__)
+
+# =============================================================================
+def pp(value, indent=4, width=99, depth=None):
+    """
+    Returns a pretty print string of the given value.
+
+    @return: pretty print string
+    @rtype: str
+    """
+
+    pretty_printer = pprint.PrettyPrinter(
+        indent=indent, width=width, depth=depth)
+    return pretty_printer.pformat(value)
+
+
+# =============================================================================
+def terminal_can_colors(debug=False):
+    """
+    Method to detect, whether the current terminal (stdout and stderr)
+    is able to perform ANSI color sequences.
+
+    @return: both stdout and stderr can perform ANSI color sequences
+    @rtype: bool
+
+    """
+
+    cur_term = ''
+    if 'TERM' in os.environ:
+        cur_term = os.environ['TERM'].lower().strip()
+
+    colored_term_list = (
+        r'ansi',
+        r'linux.*',
+        r'screen.*',
+        r'[xeak]term.*',
+        r'gnome.*',
+        r'rxvt.*',
+        r'interix',
+    )
+    term_pattern = r'^(?:' + r'|'.join(colored_term_list) + r')$'
+    re_term = re.compile(term_pattern)
+
+    ansi_term = False
+    env_term_has_colors = False
+
+    if cur_term:
+        if cur_term == 'ansi':
+            env_term_has_colors = True
+            ansi_term = True
+        elif re_term.search(cur_term):
+            env_term_has_colors = True
+    if debug:
+        sys.stderr.write(
+            "ansi_term: %r, env_term_has_colors: %r\n" % (
+                ansi_term, env_term_has_colors))
+
+    has_colors = False
+    if env_term_has_colors:
+        has_colors = True
+    for handle in [sys.stdout, sys.stderr]:
+        if (hasattr(handle, "isatty") and handle.isatty()):
+            if debug:
+                sys.stderr.write("%s is a tty.\n" % (handle.name))
+            if (platform.system() == 'Windows' and not ansi_term):
+                if debug:
+                    sys.stderr.write("platform is Windows and not ansi_term.\n")
+                has_colors = False
+        else:
+            if debug:
+                sys.stderr.write("%s is not a tty.\n" % (handle.name))
+            if ansi_term:
+                pass
+            else:
+                has_colors = False
+
+    return has_colors
+
+
+# =============================================================================
+def to_unicode(obj, encoding='utf-8'):
+
+    do_decode = False
+    if six.PY2:
+        if isinstance(obj, str):
+            do_decode = True
+    else:
+        if isinstance(obj, bytes):
+            do_decode = True
+
+    if do_decode:
+        obj = obj.decode(encoding)
+
+    return obj
+
+
+# =============================================================================
+def to_utf8(obj):
+
+    return encode_or_bust(obj, 'utf-8')
+
+
+# =============================================================================
+def encode_or_bust(obj, encoding='utf-8'):
+
+    do_encode = False
+    if six.PY2:
+        if isinstance(obj, unicode):
+            do_encode = True
+    else:
+        if isinstance(obj, str):
+            do_encode = True
+
+    if do_encode:
+        obj = obj.encode(encoding)
+
+    return obj
+
+
+# =============================================================================
+def to_bytes(obj, encoding='utf-8'):
+    "Wrapper for encode_or_bust()"
+
+    return encode_or_bust(obj, encoding)
+
+
+# =============================================================================
+def to_str(obj, encoding='utf-8'):
+    """
+    Transformes the given string-like object into the str-type according
+    to the current Python version.
+    """
+
+    if six.PY2:
+        return encode_or_bust(obj, encoding)
+    else:
+        return to_unicode_or_bust(obj, encoding)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
diff --git a/pp_lib/errors.py b/pp_lib/errors.py
new file mode 100644 (file)
index 0000000..481af30
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@summary: module for some common used error classes
+"""
+
+# Standard modules
+import errno
+
+
+__version__ = '0.2.2'
+
+# =============================================================================
+class PpError(Exception):
+    """
+    Base error class for all other self defined exceptions.
+    """
+
+    pass
+
+
+# =============================================================================
+class PpAppError(PpError):
+
+    pass
+
+
+# =============================================================================
+class InvalidMailAddressError(PpError):
+    """Class for a exception in case of a malformed mail address."""
+
+    # -------------------------------------------------------------------------
+    def __init__(self, address, msg=None):
+
+        self.address = address
+        self.msg = msg
+
+    # -------------------------------------------------------------------------
+    def __str__(self):
+
+        msg = "Wrong mail address {a!r} ({c})".format(
+            a=self.address, c=self.address.__class__.__name__)
+        if self.msg:
+            msg += ': ' + self.msg
+        else:
+            msg += '.'
+        return msg
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
diff --git a/pp_lib/mailaddress.py b/pp_lib/mailaddress.py
new file mode 100644 (file)
index 0000000..09b5090
--- /dev/null
@@ -0,0 +1,279 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2010 - 2016 by Frank Brehm, ProfitBricks GmbH, Berlin
+@summary: The module for the MailAddress object.
+"""
+
+# Standard modules
+import sys
+import os
+import logging
+import re
+
+# Own modules
+from pp_lib.errors import InvalidMailAddressError
+
+from pp_lib.common import to_str
+
+__version__ = '0.2.2'
+log = logging.getLogger(__name__)
+
+
+# =============================================================================
+class MailAddress(object):
+    """
+    Class for encapsulating a mail simple address.
+    """
+
+    pattern_valid_domain = r'@((?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z][a-z]+)$'
+
+    pattern_valid_user = r'^([a-z0-9][a-z0-9_\-\.\+\&@]*[a-z0-9]'
+    pattern_valid_user += r'(?:\+[a-z0-9][a-z0-9_\-\.]*[a-z0-9])*)'
+
+    pattern_valid_address = pattern_valid_user + pattern_valid_domain
+
+    re_valid_user = re.compile(pattern_valid_user + r'$', re.IGNORECASE)
+    re_valid_domain = re.compile(r'^' + pattern_valid_domain, re.IGNORECASE)
+    re_valid_address = re.compile(pattern_valid_address, re.IGNORECASE)
+
+    verbose = 0
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def valid_address(cls, address, raise_on_failure=False):
+
+        if not address:
+            e = InvalidMailAddressError(address, "Empty address.")
+            if raise_on_failure:
+                raise e
+            elif cls.verbose > 2:
+                log.debug(str(e))
+            return False
+
+        addr = to_str(address)
+        if not isinstance(addr, str):
+            e = InvalidMailAddressError(address, "Wrong type.")
+            if raise_on_failure:
+                raise e
+            elif cls.verbose > 2:
+                log.debug(str(e))
+            return False
+
+        if cls.re_valid_address.search(addr):
+            return True
+
+        e = InvalidMailAddressError(address, "Invalid address.")
+        if raise_on_failure:
+            raise e
+        elif cls.verbose > 2:
+            log.debug(str(e))
+        return False
+
+    # -------------------------------------------------------------------------
+    def __init__(self, user=None, domain=None):
+
+        self._user = ''
+        self._domain = ''
+
+        if not domain:
+            if user:
+                addr = to_str(user)
+                if self.valid_address(addr):
+                    match = self.re_valid_address.search(addr)
+                    self._user = match.group(1)
+                    self._domain = match.group(2)
+                    return
+                match = self.re_valid_domain.search(addr)
+                if match:
+                    self._domain = match.group(1)
+                    return
+                self._user = addr
+                return
+
+        self._user = to_str(user)
+        self._domain = to_str(domain)
+
+    # -----------------------------------------------------------
+    @property
+    def user(self):
+        """The user part of the address."""
+        if self._user is None:
+            return ''
+        return self._user
+
+    # -----------------------------------------------------------
+    @property
+    def domain(self):
+        """The domain part of the address."""
+        if self._domain is None:
+            return ''
+        return self._domain
+
+    # -------------------------------------------------------------------------
+    def __str__(self):
+
+        if not self.user and not self.domain:
+            return ''
+
+        if not self.domain:
+            return self.user
+
+        if not self.user:
+            return '@' + self.domain
+
+        return self.user + '@' + self.domain
+
+    # -------------------------------------------------------------------------
+    def str_for_access(self):
+
+        if not self.user and not self.domain:
+            return None
+
+        if not self.domain:
+            return self.user + '@'
+
+        if not self.user:
+            return self.domain
+
+        return self.user + '@' + self.domain
+
+    # -------------------------------------------------------------------------
+    def __repr__(self):
+        """Typecasting into a string for reproduction."""
+
+        out = "<%s(" % (self.__class__.__name__)
+
+        fields = []
+        fields.append("user={!r}".format(self.user))
+        fields.append("domain={!r}".format(self.domain))
+
+        out += ", ".join(fields) + ")>"
+        return out
+
+
+    # -------------------------------------------------------------------------
+    def __hash__(self):
+        return hash(str(self).lower())
+
+    # -------------------------------------------------------------------------
+    def __eq__(self, other):
+
+        if not isinstance(other, MailAddress):
+            if other is None:
+                return False
+            return str(self).lower() == str(other).lower()
+
+        if not self.user:
+            if other.user:
+                return False
+            if not self.domain:
+                if other.domain:
+                    return False
+                return True
+            if not other.domain:
+                return False
+            if self.domain.lower() == other.domain.lower():
+                return True
+            return False
+
+        if not self.domain:
+            if other.domain:
+                return False
+            if not other.user:
+                return False
+            if self.user.lower() == other.user.lower():
+                return True
+            return False
+
+        if not other.user:
+            return False
+        if not other.domain:
+            return False
+        if self.domain.lower() != other.domain.lower():
+            return False
+        if self.user.lower() != other.user.lower():
+            return False
+
+        return True
+
+    # -------------------------------------------------------------------------
+    def __ne__(self, other):
+
+        if self == other:
+            return False
+        return True
+
+    # -------------------------------------------------------------------------
+    def __lt__(self, other):
+
+        if not isinstance(other, MailAddress):
+            if other is None:
+                return False
+            return str(self).lower() < str(other).lower()
+
+        if not self.user:
+            if not self.domain:
+                if other.domain:
+                    return False
+                return True
+            if not other.domain:
+                return False
+            if self.domain.lower() != other.domain.lower():
+                return self.domain.lower() < other.domain.lower()
+            if other.user:
+                return False
+            return True
+
+        if not self.domain:
+            if other.domain:
+                return True
+            if not other.user:
+                return False
+            if self.user.lower() != other.user.lower():
+                return self.user.lower() < other.user.lower()
+            return False
+
+        if not other.domain:
+            return False
+        if not other.user:
+            return False
+
+        if self.domain.lower() != other.domain.lower():
+            return self.domain.lower() < other.domain.lower()
+        if self.user.lower() != other.user.lower():
+            return self.user.lower() < other.user.lower()
+
+        return False
+
+    # -------------------------------------------------------------------------
+    def __gt__(self, other):
+
+        if not isinstance(other, MailAddress):
+            return NotImplemented
+
+        if self < other:
+            return False
+        return True
+
+    # -------------------------------------------------------------------------
+    def __copy__(self):
+        "Implementing a wrapper for copy.copy()."
+
+        addr = MailAddress()
+        addr._user = self.user
+        addr._domain = self.domain
+        return addr
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list