Created
January 9, 2019 13:42
-
-
Save BaylorRae/288b775e03ad4d3667ec7d827fdb674f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def test_ordered_reducer_pattern(): | |
class RandomNumberDependency(Dependency): | |
def promises(self): | |
return ['random-number'] | |
def execute(self, context): | |
context['random-number'] = 5 | |
return context | |
class BigRandomNumberDependency(Dependency): | |
def expects(self): | |
return ['random-number'] | |
def promises(self): | |
return ['big-random-number'] | |
def execute(self, context): | |
context['big-random-number'] = context['random-number'] * 5 | |
return context | |
dependencies = [ | |
RandomNumberDependency, | |
BigRandomNumberDependency | |
] | |
needs = ['big-random-number'] | |
result, history = satisfy_needs(dependencies, needs) | |
assert result == { | |
'random-number': 5, | |
'big-random-number': 25 | |
} | |
assert history == [ | |
{}, | |
{'random-number': 5}, | |
{'random-number': 5, 'big-random-number': 25} | |
] | |
def test_more_dependencies(): | |
class Dep1(Dependency): | |
def promises(self): | |
return ['thing-1'] | |
def execute(self, context): | |
context['thing-1'] = 1 | |
return context | |
class Dep2(Dependency): | |
def promises(self): | |
return ['thing-2'] | |
def expects(self): | |
return ['thing-1'] | |
def execute(self, context): | |
context['thing-2'] = context['thing-1'] * 2 | |
return context | |
class Dep3(Dependency): | |
def promises(self): | |
return ['thing-3-from-4-2-1'] | |
def expects(self): | |
return ['thing-1', 'thing-4.1', 'thing-2', 'thing-4.2'] | |
def execute(self, context): | |
context['thing-3-from-4-2-1'] = sum(context[req] for req in self.expects()) | |
return context | |
class Dep4(Dependency): | |
def promises(self): | |
return ['thing-4.1', 'thing-4.2'] | |
def expects(self): | |
return ['thing-2', 'thing-1'] | |
def execute(self, context): | |
context['thing-4.1'] = context['thing-2'] * 2 | |
context['thing-4.2'] = context['thing-1'] + context['thing-2'] | |
return context | |
dependencies = [Dep1, Dep2, Dep3, Dep4] | |
needs = ['thing-3-from-4-2-1'] | |
result, history = satisfy_needs(dependencies, needs) | |
assert result == { | |
'thing-1': 1, | |
'thing-2': 2, | |
'thing-3-from-4-2-1': 10, | |
'thing-4.1': 4, | |
'thing-4.2': 3 | |
} | |
assert history == [ | |
{}, | |
{'thing-1': 1}, | |
{'thing-1': 1, 'thing-2': 2}, | |
{'thing-1': 1, 'thing-2': 2, 'thing-4.1': 4, 'thing-4.2': 3}, | |
{'thing-1': 1, 'thing-2': 2, 'thing-4.1': 4, 'thing-4.2': 3, 'thing-3-from-4-2-1': 10}, | |
] | |
def test_stop_early(): | |
""" | |
I'm not sure if we should return the modified context when stopping early or not. | |
""" | |
class RandomNumberDependency(Dependency): | |
def promises(self): | |
return ['random-number'] | |
def execute(self, context): | |
context['random-number'] = 5 | |
return context | |
class BigRandomNumberDependency(Dependency): | |
def expects(self): | |
return ['random-number', 'finished-early'] | |
def promises(self): | |
return ['big-random-number'] | |
def execute(self, context): | |
context['big-random-number'] = context['random-number'] * 5 | |
return context | |
class EarlyFinishDependency(Dependency): | |
def expects(self): | |
return ['random-number'] | |
def promises(self): | |
return ['finished-early'] | |
def execute(self, context): | |
context['finished-early'] = True | |
raise StopEarly() | |
return context | |
dependencies = [ | |
RandomNumberDependency, | |
BigRandomNumberDependency, | |
EarlyFinishDependency | |
] | |
needs = ['big-random-number'] | |
result, history = satisfy_needs(dependencies, needs) | |
assert result == { | |
'random-number': 5 | |
} | |
assert history == [ | |
{}, | |
{'random-number': 5} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import reduce | |
class Dependency: | |
def expects(self): | |
return [] | |
def promises(self): | |
return [] | |
def execute(self, context): | |
return context | |
class StopEarly(Exception): | |
pass | |
class Runner: | |
def __init__(self): | |
self.result = None | |
self.history = [] | |
def run(self, tasks, initial_context={}): | |
self.history = [initial_context] | |
try: | |
reduce(self.run_task, tasks, initial_context) | |
except StopEarly: | |
pass | |
return self.result | |
def run_task(self, context, task_class): | |
self.result = task_class.execute(context.copy()) | |
self.history = [*self.history, self.result] | |
return self.result | |
def reducer_pattern(tasks, initial_context={}): | |
runner = Runner() | |
return runner.run(tasks, initial_context), runner.history | |
def build_requirements(promises, needs): | |
for need in needs: | |
if need not in promises: | |
raise RuntimeError(f'Could not find {need}') | |
dependency = promises[need] | |
for requirement in build_requirements(promises, dependency.expects()): | |
yield requirement | |
yield dependency | |
def simplify_requirements(tasks): | |
seen = [] | |
for task in tasks: | |
if task in seen: | |
continue | |
seen.append(task) | |
yield task | |
def satisfy_needs(dependencies, needs): | |
promises = { | |
promise: dependency | |
for dependency in [d() for d in dependencies] | |
for promise in dependency.promises() | |
} | |
tasks = simplify_requirements(build_requirements(promises, needs)) | |
return reducer_pattern(tasks, {}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment