lliendo/Radar

View on GitHub
radar/class_loader/__init__.py

Summary

Maintainability
A
0 mins
Test Coverage
# -*- coding: utf-8 -*-

"""
This file is part of Radar.

Radar is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Radar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Radar. If not, see <http://www.gnu.org/licenses/>.

Copyright 2015 Lucas Liendo.
"""


from io import open
from os.path import join as join_path
from ast import parse as ast_parse
from ast import walk as ast_walk
from ast import ClassDef
from pkgutil import iter_modules
from sys import path as module_search_path
from ..logger import RadarLogger


class ClassLoaderError(Exception):
    pass


class ClassLoader(object):
    """
    This class offers a simple mechanism to get all user-defined
    classes from an external module. This is useful if you want
    to load unknown classes dynamically at run-time.
    """

    ENCODING_DECLARATION = '# -*- coding: utf-8 -*-'

    def __init__(self, module_path):
        self._module_path = module_path
        module_search_path.append(module_path)

    def _get_class_names(self, filename):
        class_names = []

        try:
            with open(filename) as fd:
                parsed_source = ast_parse(fd.read().strip(self.ENCODING_DECLARATION))
                class_names = [n.name for n in ast_walk(parsed_source) if isinstance(n, ClassDef)]
        except IOError as e:
            raise ClassLoaderError('Error - Couldn\'t open : \'{:}\'. Reason : {:}.'.format(filename, e.strerror))
        except SyntaxError as e:
            raise ClassLoaderError('Error - Couldn\'t parse \'{:}\'. Reason: {:}.'.format(filename, e))

        return class_names

    def get_classes(self, subclass=object):
        classes = []

        for _, module_name, _ in iter_modules(path=[self._module_path]):
            module_path = join_path(self._module_path, module_name)

            try:
                class_names = self._get_class_names(module_path + '/__init__.py')
                imported_module = __import__(module_name)
                classes += [getattr(imported_module, class_name) for class_name in class_names]
            except ClassLoaderError as e:
                RadarLogger.log(e)

        return [C for C in classes if issubclass(C, subclass)]