Peter-Slump/django-dynamic-fixtures

View on GitHub
src/dynamic_fixtures/fixtures/loader.py

Summary

Maintainability
A
1 hr
Test Coverage
import logging
import os
from importlib import import_module

from django.apps import apps

logger = logging.getLogger(__name__)
FIXTURES_MODULE_NAME = "fixtures"


class Loader(object):
    def __init__(self):
        self.disk_fixtures = None

    @classmethod
    def fixtures_module(cls, app_label):
        app_package_name = apps.get_app_config(app_label).name
        return "%s.%s" % (app_package_name, FIXTURES_MODULE_NAME)

    def load_disk(self):

        self.disk_fixtures = {}

        for app_config in apps.get_app_configs():
            # No models no need for fixtures
            if app_config.models_module is None:
                logger.info("No models found for {}".format(app_config.label))

            self.handle_app_config(app_config=app_config)

    def handle_app_config(self, app_config):

        # Get the fixtures module directory
        module_name = self.fixtures_module(app_config.label)

        directory = self.get_module_directory(module_name=module_name)

        if directory is None:
            return

        fixture_names = self.get_fixture_files(directory=directory)

        # Load them
        for fixture_name in fixture_names:
            fixture_module = import_module("%s.%s" % (module_name, fixture_name))
            if not hasattr(fixture_module, "Fixture"):
                logger.error(
                    "Fixture %s in app %s has no Fixture class"
                    % (fixture_name, app_config.label)
                )
                continue
            self.disk_fixtures[app_config.label, fixture_name] = fixture_module.Fixture(
                fixture_name, app_config.label
            )

    @staticmethod
    def get_fixture_files(directory):
        # Scan for .py files
        fixture_names = set()
        for name in os.listdir(directory):
            if name.endswith(".py"):
                import_name = name.rsplit(".", 1)[0]
                if import_name[0] not in "_.~":
                    fixture_names.add(import_name)
        return fixture_names

    @staticmethod
    def get_module_directory(module_name):
        try:
            module = import_module(module_name)
        except ImportError as e:
            # I hate doing this, but I don't want to squash other import
            # errors. Might be better to try a directory check directly.
            if "No module named" in str(e) and FIXTURES_MODULE_NAME in str(e):
                return
            raise

        try:
            directory = os.path.dirname(module.__file__)
        except AttributeError:
            # No __file__ available for module
            return

        return directory


class Graph(object):
    """A dependency graph
    """

    def __init__(self):
        self._nodes = {}

    @property
    def nodes(self):
        return self._nodes

    def add_node(self, node):
        self._nodes.setdefault(node, list())

    def add_dependency(self, node, dependency):
        if node not in self._nodes:
            raise KeyError("Node %s not set", str(node))
        if dependency not in self._nodes:
            raise KeyError(
                'Dependency "%s" required for "%s" but is not set.'
                % (str(dependency), str(node))
            )
        self._nodes[node].append(dependency)

    def __iter__(self):
        for resolved_node in self.resolve_node():
            yield resolved_node

    def resolve_nodes(self, nodes):
        """
        Resolve a given set of nodes.

        Dependencies of the nodes, even if they are not in the given list will
        also be resolved!

        :param list nodes: List of nodes to be resolved
        :return: A list of resolved nodes
        """
        if not nodes:
            return []
        resolved = []
        for node in nodes:
            if node in resolved:
                continue
            self.resolve_node(node, resolved)
        return resolved

    def resolve_node(self, node=None, resolved=None, seen=None):
        """
        Resolve a single node or all when node is omitted.
        """
        if seen is None:
            seen = []
        if resolved is None:
            resolved = []
        if node is None:
            dependencies = sorted(self._nodes.keys())
        else:
            dependencies = self._nodes[node]
            seen.append(node)
        for dependency in dependencies:
            if dependency in resolved:
                continue
            if dependency in seen:
                raise Exception(
                    "Circular dependency %s > %s", str(node), str(dependency)
                )
            self.resolve_node(dependency, resolved, seen)
        if node is not None:
            resolved.append(node)
        return resolved