src/dynamic_fixtures/fixtures/runner.py
import time
from django.db import transaction
from dynamic_fixtures.fixtures.exceptions import (
DryRun,
FixtureNotFound,
MultipleFixturesFound,
)
from dynamic_fixtures.fixtures.loader import Graph, Loader
class LoadFixtureRunner(object):
def __init__(self):
self.loader = Loader()
self.loader.load_disk()
self._graph = None
@property
def graph(self):
if not self._graph:
self.init_graph()
return self._graph
def init_graph(self):
"""
Initialize graph
Load all nodes and set dependencies.
To avoid errors about missing nodes all nodes get loaded first before
setting the dependencies.
"""
self._graph = Graph()
# First add all nodes
for key in self.loader.disk_fixtures.keys():
self.graph.add_node(key)
# Then set dependencies
for key, fixture in self.loader.disk_fixtures.items():
for dependency in fixture.dependencies:
self.graph.add_dependency(key, dependency)
def get_app_nodes(self, app_label):
"""
Get all nodes for given app
:param str app_label: app label
:rtype: list
"""
return [node for node in self.graph.nodes if node[0] == app_label]
def get_fixture_node(self, app_label, fixture_prefix):
"""
Get all fixtures in given app with given prefix.
:param str app_label: App label
:param str fixture_prefix: first part of the fixture name
:return: list of found fixtures.
"""
app_nodes = self.get_app_nodes(app_label=app_label)
nodes = [node for node in app_nodes if node[1].startswith(fixture_prefix)]
if len(nodes) > 1:
raise MultipleFixturesFound(
"The following fixtures with prefix '%s' are found in app '%s'"
": %s"
% (fixture_prefix, app_label, ", ".join([node[1] for node in nodes]))
)
elif len(nodes) == 0:
raise FixtureNotFound(
"Fixture with prefix '%s' not found in app "
"'%s'" % (fixture_prefix, app_label)
)
return nodes
def load_fixtures(self, nodes=None, progress_callback=None, dry_run=False):
"""Load all fixtures for given nodes.
If no nodes are given all fixtures will be loaded.
:param list nodes: list of nodes to be loaded.
:param callable progress_callback: Callback which will be called while
handling the nodes.
"""
if progress_callback and not callable(progress_callback):
raise Exception("Callback should be callable")
plan = self.get_plan(nodes=nodes)
try:
with transaction.atomic():
self.load_plan(plan=plan, progress_callback=progress_callback)
if dry_run:
raise DryRun
except DryRun:
# Dry-run to get the atomic transaction rolled back
pass
return len(plan)
def load_plan(self, plan, progress_callback):
# Load every fixture in the plan.
for node in plan:
if progress_callback:
progress_callback("load_start", node)
start = time.time()
self.loader.disk_fixtures[node].load()
if progress_callback:
progress_callback("load_success", node, time.time() - start)
def get_plan(self, nodes=None):
"""
Retrieve a plan, e.g. a list of fixtures to be loaded sorted on
dependency.
:param list nodes: list of nodes to be loaded.
:return:
"""
if nodes:
plan = self.graph.resolve_nodes(nodes)
else:
plan = self.graph.resolve_node()
return plan