예제 #1
0
 def test_get_dir(self) -> None:
     app_name = env.get_var('name') or 'no name'
     app_author = env.get_var('author') or 'no author'
     keys = set(self.app_dirs + self.dist_dirs + self.pkg_dirs)
     for key in keys:
         with self.subTest(f"get_dir('{key}')"):
             path = pathlib.Path(env.get_dir(key))
             is_valid = self.is_dir_valid(key, path, app_name, app_author)
             self.assertTrue(is_valid)
예제 #2
0
def run_shell() -> None:
    """Start rian session in IPython interactive shell."""
    name = env.get_var('name') or ''
    version = env.get_var('version') or ''
    banner = name + ' ' + version
    shell.run(banner=banner)
예제 #3
0
def print_version() -> None:
    """Print rian version to standard output."""
    version = env.get_var('version') or ''
    ui.info('rian ' + version)
예제 #4
0
 def test_get_dirs(self) -> None:
     app_name = env.get_var('name') or 'no name'
     app_author = env.get_var('author') or 'no author'
     app_dirs = env.get_dirs()
     is_valid = self.is_dirs_valid(app_dirs, app_name, app_author)
     self.assertTrue(is_valid)
예제 #5
0
파일: log.py 프로젝트: fd0livar/rian
class Logger(attrib.Group, abc.Singleton):
    """Singleton class for logging.

    Args:
        name: String identifier of Logger, given as a period-separated
            hierarchical value like 'foo.bar.baz'. The name of a Logger also
            identifies respective parents and children by the name hierachy,
            which equals the Python package hierarchy.
        file: String or :term:`path-like object` that identifies a valid
            filename in the directory structure of the operating system. If they
            do not exist, the parent directories of the file are created. If no
            file is given, a default logfile within the applications
            *user-log-dir* is created. If the logfile can not be created a
            temporary logfile in the systems *temp* folder is created as a
            fallback.
        level: Integer value or string, which describes the minimum required
            severity of events, to be logged. Ordered by ascending severity, the
            allowed level names are: 'DEBUG', 'INFO', 'WARNING', 'ERROR' and
            'CRITICAL'. The respectively corresponding level numbers are 10, 20,
            30, 40 and 50. The default level is 'INFO'.

    """

    #
    # Protected Class Variables
    #

    _level_names: ClassVar[StrList] = [
        'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
    _default_name: ClassVar[str] = env.get_var('name') or __name__
    _default_file: ClassVar[Path] = Path(
        env.get_dir('user_log_dir'), _default_name + '.log')
    _default_level: ClassVar[StrOrInt] = logging.INFO

    #
    # Public Attributes
    #

    logger: property = attrib.Virtual(
        '_get_logger', '_set_logger', dtype=logging.Logger)

    name: property = attrib.Virtual('_get_name', '_set_name', dtype=str)
    name.__doc__ = """
    String identifier of Logger, given as a period-separated hierarchical value
    like 'foo.bar.baz'. The name of a Logger also identifies respective parents
    and children by the name hierachy, which equals the Python package
    hierarchy.
    """

    file: property = attrib.Virtual('_get_file', '_set_file', dtype=(str, Path))
    file.__doc__ = """
    String or :term:`path-like object` that identifies a valid filename in the
    directory structure of the operating system. If they do not exist, the
    parent directories of the file are created. If no file is given, a default
    logfile within the applications *user-log-dir* is created. If the logfile
    can not be created a temporary logfile in the systems *temp* folder is
    created as a fallback.
    """

    level: property = attrib.Virtual(
        '_get_level', '_set_level', dtype=(str, int))
    level.__doc__ = """
    Integer value or string, which describes the minimum required severity of
    events, to be logged. Ordered by ascending severity, the allowed level names
    are: 'DEBUG', 'INFO', 'WARNING', 'ERROR' and 'CRITICAL'. The respectively
    corresponding level numbers are 10, 20, 30, 40 and 50. The default level is
    'INFO'.
    """

    #
    # Protected Attributes
    #

    _logger: property = attrib.Temporary(dtype=logging.Logger)

    #
    # Special Methods
    #

    def __init__(
            self, name: str = _default_name, file: PathLike = _default_file,
            level: StrOrInt = _default_level) -> None:
        super().__init__() # Initialize Attribute Container
        self._start_logging(name=name, file=file, level=level) # Start logging

    def __del__(self) -> None:
        self._stop_logging() # Stop logging

    def __str__(self) -> str:
        return str(self.logger)

    #
    # Public Methods
    #

    def log(self, level: StrOrInt, msg: str, *args: Any, **kwds: Any) -> None:
        """Log event.

        Args:
            level: Integer value or string, which describes the severity of the
                event. In the order of ascending severity, the accepted level
                names are: 'DEBUG', 'INFO', 'WARNING', 'ERROR' and 'CRITICAL'.
                The respectively corresponding level numbers are 10, 20, 30, 40
                and 50.
            msg: Message :ref:`format string <formatstrings>`, containing
                literal text or braces delimited replacement fields. Each
                replacement field contains either the numeric index of a
                positional argument, given by *args, or the name of a keyword
                argument, given by the keyword *extra*.
            *args: Arguments, which can be used by the message format string.
            **kwds: Additional Keywords, used by :meth:`logging.Logger.log`.

        """
        if isinstance(level, str):
            level = self._get_level_number(level)
        self.logger.log(level, msg, *args, **kwds)

    #
    # Protected Methods
    #

    def _start_logging(
            self, name: str = _default_name, file: PathLike = _default_file,
            level: StrOrInt = _default_level) -> bool:
        logger = logging.getLogger(name) # Create new Logger instance
        self._set_logger(logger) # Bind Logger instance to global variable
        self._set_level(level) # Set log level
        self._set_file(file) # Add file handler for logfile
        if not self.file.is_file(): # Stop logging if an error occured
            self._stop_logging()
            return False
        return True

    def _stop_logging(self) -> None:
        for handler in self.logger.handlers: # Close file handlers
            with contextlib.suppress(AttributeError):
                handler.close()
        self._logger = None

    def _get_logger(self, auto_start: bool = True) -> logging.Logger:
        if not self._logger:
            if auto_start:
                self._start_logging()
            else:
                raise NotExistsError("logging has not been started")
        return self._logger

    def _set_logger(
            self, logger: logging.Logger, auto_stop: bool = True) -> None:
        if self._logger:
            if auto_stop:
                self._stop_logging()
            else:
                raise ExistsError("logging has already been started")
        self._logger = logger

    def _get_name(self) -> str:
        return self.logger.name

    def _set_name(self, name: str) -> None:
        self.logger.name = name

    def _get_file(self) -> OptPath:
        for handler in self.logger.handlers:
            with contextlib.suppress(AttributeError):
                return Path(handler.baseFilename)
        return None

    def _set_file(self, filepath: PathLike = _default_file) -> None:
        # Locate valid logfile
        logfile = self._locate_logfile(filepath)
        if not isinstance(logfile, Path):
            warnings.warn("could not set logfile")
            return None

        # Close and remove all previous file handlers
        if self.logger.hasHandlers():
            remove = [h for h in self.logger.handlers if hasattr(h, 'close')]
            for handler in remove:
                handler.close()
                self.logger.removeHandler(handler)

        # Add file handler for logfile
        handers = importlib.import_module('logging.handlers')
        handler = getattr(handers, 'TimedRotatingFileHandler')(
            str(logfile), when="d", interval=1, backupCount=5)
        formatter = logging.Formatter(
            fmt="%(asctime)s %(levelname)s %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S")
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
        return None

    def _get_level(self, as_name: bool = True) -> StrOrInt:
        level = getattr(self.logger, 'getEffectiveLevel')()
        if not as_name:
            return level
        return self._get_level_name(level)

    def _get_level_name(self, level: int) -> str:
        names = self._level_names
        return names[int(max(min(level, 50), 0) / 10)]

    def _get_level_number(self, name: str) -> int:
        name = name.upper()
        names = self._level_names
        if not name in names:
            allowed = ', '.join(names[1:])
            raise ValueError(
                f"{name} is not a valid level name, "
                f"allowed values are: {allowed}")
        return names.index(name) * 10

    def _set_level(self, level: StrOrInt) -> None:
        if isinstance(level, str):
            level = level.upper()
        getattr(self.logger, 'setLevel')(level)

    def _locate_logfile(
            self, filepath: PathLike = _default_file) -> OptPath:
        # Get valid logfile from filepath
        if isinstance(filepath, (str, Path)):
            logfile = env.expand(filepath)
            if env.touch(logfile):
                return logfile

        # Get temporary logfile
        logfile = env.get_temp_file(suffix='log')
        if env.touch(logfile):
            warnings.warn(
                f"logfile '{filepath}' is not valid: "
                f"using temporary logfile '{logfile}'")
            return logfile
        return None
예제 #6
0
파일: parser.py 프로젝트: fd0livar/hup
 def _unescape(self, key: str, pos: int) -> str:
     encoding = env.get_var('encoding') or 'UTF-8'
     return key.encode(encoding).decode('unicode_escape')