-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
actionpack#65: "adds pipeline abstraction" (#77)
* introduces ActionType metaclass * introduces the Pipeline abstraction * Pipeline made into an Action * rectifies Serialization.validate return value issue * updates WriteBytes.instruction the handle "append" mode without decoding bytes * WriteBytes.instruction returns absolute filepath where bytes were successfully written * finalizes the Pipeline implementation
- Loading branch information
1 parent
ca5c9ab
commit 6148eaf
Showing
9 changed files
with
198 additions
and
17 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from collections import OrderedDict | ||
from inspect import signature | ||
from typing import Optional | ||
|
||
from actionpack import Action | ||
from actionpack.action import ActionType | ||
from actionpack.actions import Call | ||
|
||
|
||
class Pipeline(Action): | ||
|
||
def __init__(self, action: Action, *action_types: ActionType, should_raise: bool = False): | ||
self.action = action | ||
self.should_raise = should_raise | ||
|
||
for action_type in action_types: | ||
if not isinstance(action_type, ActionType): | ||
raise TypeError(f'Must be an {ActionType.__name__}: {action_type}') | ||
|
||
self.action_types = action_types | ||
self._action_types = iter(self.action_types) | ||
|
||
def instruction(self): | ||
return self.flush(self.action).perform(should_raise=self.should_raise).value | ||
|
||
# recursive | ||
def flush(self, given_action: Optional[Action] = None) -> Action: | ||
next_action_type = next(self) | ||
|
||
if next_action_type: | ||
params_dict = OrderedDict(signature(next_action_type.__init__).parameters.items()) | ||
params_dict.pop('self', None) | ||
params = list(params_dict.keys()) | ||
keyed_result = dict(zip(params, [given_action.perform(should_raise=self.should_raise).value])) | ||
if next_action_type.__name__ == Pipeline.Fitting.__name__: | ||
params_dict = OrderedDict(signature(next_action_type.action.__init__).parameters.items()) | ||
params_dict.pop('self', None) | ||
params = list(params_dict.keys()) | ||
keyed_result = dict(zip(params, [keyed_result['action']])) | ||
next_action_type.kwargs.update(keyed_result) | ||
next_action = next_action_type.action(**next_action_type.kwargs) | ||
else: | ||
next_action = next_action_type(**keyed_result) | ||
|
||
return self.flush(next_action) | ||
|
||
return given_action | ||
|
||
def __iter__(self): | ||
return self._action_types | ||
|
||
def __next__(self): | ||
try: | ||
return next(self._action_types) | ||
except StopIteration: | ||
self._action_types = iter(self._action_types) | ||
|
||
class Fitting(type): | ||
|
||
@staticmethod | ||
def init( | ||
action: Action, | ||
should_raise: bool = False, | ||
reaction: Call = None, | ||
*args, **kwargs | ||
): | ||
pass | ||
|
||
@staticmethod | ||
def instruction(instance): | ||
action_performance = instance.action.perform(should_raise=instance.should_raise) | ||
if action_performance.successful and instance.reaction: | ||
return instance.reaction.perform(should_raise=instance.should_raise) | ||
return action_performance | ||
|
||
def __new__( | ||
mcs, | ||
action: Action, | ||
should_raise: bool = False, | ||
reaction: Call = None, | ||
**kwargs | ||
): | ||
dct = dict() | ||
dct['__init__'] = Pipeline.Fitting.init | ||
dct['instruction'] = Pipeline.Fitting.instruction | ||
dct['action'] = action | ||
dct['should_raise'] = should_raise | ||
dct['reaction'] = reaction | ||
dct['kwargs'] = kwargs | ||
cls = type(Pipeline.Fitting.__name__, (Action,), dct) | ||
return cls |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from unittest import TestCase | ||
from unittest.mock import patch | ||
|
||
from actionpack.action import Result | ||
from actionpack.actions import Pipeline | ||
from actionpack.actions import ReadInput | ||
from actionpack.actions import ReadBytes | ||
from actionpack.actions import WriteBytes | ||
from actionpack.actions.pipeline import Call | ||
from actionpack.utils import Closure | ||
from tests.actionpack import FakeFile | ||
|
||
|
||
class PipelineTest(TestCase): | ||
|
||
@patch('pathlib.Path.open') | ||
@patch('pathlib.Path.exists') | ||
@patch('builtins.input') | ||
def test_Pipeline(self, mock_input, mock_exists, mock_file): | ||
filename = 'this/file.txt' | ||
contents = b"What's wrong with him? ...My first thought would be, 'a lot'." | ||
file = FakeFile(contents) | ||
|
||
mock_file.return_value = file | ||
mock_exists.return_value = True | ||
mock_input.return_value = filename | ||
|
||
pipeline = Pipeline(ReadInput('Which file?'), ReadBytes) | ||
result = pipeline.perform() | ||
|
||
self.assertIsInstance(result, Result) | ||
self.assertEqual(result.value, contents) | ||
|
||
@patch('pathlib.Path.open') | ||
@patch('pathlib.Path.exists') | ||
@patch('builtins.input') | ||
def test_Pipeline_FittingType(self, mock_input, mock_exists, mock_file): | ||
filename = 'this/file.txt' | ||
file = FakeFile() | ||
question = b'How are you?' | ||
reply = 'I\'m fine.' | ||
|
||
mock_file.return_value = file | ||
mock_exists.return_value = True | ||
mock_input.side_effect = [filename, reply] | ||
|
||
read_input = ReadInput('Which file?') | ||
action_types = [ | ||
Pipeline.Fitting( | ||
action=WriteBytes, | ||
reaction=Call(Closure(bytes.decode)), | ||
**{'append': True, 'bytes_to_write': question}, | ||
), | ||
ReadBytes, # retrieve question from FakeFile | ||
ReadInput # pose question to user | ||
] | ||
pipeline = Pipeline(read_input, *action_types, should_raise=True) | ||
result = pipeline.perform(should_raise=True) | ||
|
||
self.assertEqual(file.read(), question + b'\n') | ||
self.assertIsInstance(result, Result) | ||
self.assertEqual(result.value, reply) |
This file contains 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