diff --git a/screenpy/actions/attach_the_file.py b/screenpy/actions/attach_the_file.py index 609de488..2695ca0d 100644 --- a/screenpy/actions/attach_the_file.py +++ b/screenpy/actions/attach_the_file.py @@ -2,6 +2,7 @@ Attach a file. """ +import os from typing import Any from screenpy import Actor @@ -23,11 +24,16 @@ class AttachTheFile: ) """ + def describe(self) -> str: + """Describe the Action in present tense.""" + return f"Attach a file named {self.filename}." + # no beat, to make reading reports easier. def perform_as(self, _: Actor) -> None: """Direct the Narrator to attach a file.""" - the_narrator.attaches_a_file(self.path, **self.attach_kwargs) + the_narrator.attaches_a_file(self.filepath, **self.attach_kwargs) - def __init__(self, path: str, **kwargs: Any) -> None: - self.path = path + def __init__(self, filepath: str, **kwargs: Any) -> None: + self.filepath = filepath + self.filename = os.path.basename(filepath) self.attach_kwargs = kwargs diff --git a/screenpy/actions/eventually.py b/screenpy/actions/eventually.py index e84dafa2..7d87da4f 100644 --- a/screenpy/actions/eventually.py +++ b/screenpy/actions/eventually.py @@ -77,17 +77,44 @@ def perform_as(self, the_actor: "Actor") -> None: the_actor.attempts_to(self.eventually) def for_(self, amount: float) -> _TimeframeBuilder: - """Set for how long the actor should continue trying.""" + """Set for how long the actor should continue trying. + + Aliases: + * :meth:`~screenpy.actions.Eventually.trying_for_no_longer_than` + * :meth:`~screenpy.actions.Eventually.trying_for` + * :meth:`~screenpy.actions.Eventually.waiting_for` + """ return self._TimeframeBuilder(self, amount, "timeout") - trying_for_no_longer_than = trying_for = waiting_for = for_ + def trying_for_no_longer_than(self, amount: float) -> _TimeframeBuilder: + """Alias for :meth:`~screenpy.actions.Eventually.for_`.""" + return self.for_(amount) + + def trying_for(self, amount: float) -> _TimeframeBuilder: + """Alias for :meth:`~screenpy.actions.Eventually.for_`.""" + return self.for_(amount) + + def waiting_for(self, amount: float) -> _TimeframeBuilder: + """Alias for :meth:`~screenpy.actions.Eventually.for_`.""" + return self.for_(amount) def polling(self, amount: float) -> _TimeframeBuilder: - """Adjust the polling frequency.""" + """Adjust the polling frequency. + + Aliases: + * :meth:`~screenpy.actions.Eventually.polling_every` + * :meth:`~screenpy.actions.Eventually.trying_every` + """ self.poll = amount return self._TimeframeBuilder(self, amount, "poll") - trying_every = polling_every = polling + def polling_every(self, amount: float) -> _TimeframeBuilder: + """Alias for :meth:`~screenpy.actions.Eventually.polling`.""" + return self.polling(amount) + + def trying_every(self, amount: float) -> _TimeframeBuilder: + """Alias for :meth:`~screenpy.actions.Eventually.polling`.""" + return self.polling(amount) def describe(self) -> str: """Describe the Action in present tense.""" diff --git a/screenpy/actions/make_note.py b/screenpy/actions/make_note.py index cd43b899..13afdcb3 100644 --- a/screenpy/actions/make_note.py +++ b/screenpy/actions/make_note.py @@ -30,10 +30,17 @@ class MakeNote: @classmethod def of(cls, question: Union[Answerable, Any]) -> "MakeNote": - """Supply the Question to answer and its arguments.""" + """Supply the Question to answer and its arguments. + + Aliases: + * :meth:`~screenpy.actions.MakeNote.of_the` + """ return cls(question) - of_the = of + @classmethod + def of_the(cls, question: Union[Answerable, Any]) -> "MakeNote": + """Alias for :meth:`~screenpy.actions.MakeNote.of`.""" + return cls.of(question) def as_(self, key: str) -> "MakeNote": """Set the key to use to recall this noted value.""" diff --git a/screenpy/actions/pause.py b/screenpy/actions/pause.py index 6972548f..5c062783 100644 --- a/screenpy/actions/pause.py +++ b/screenpy/actions/pause.py @@ -38,12 +38,18 @@ def for_(cls, number: float) -> "Pause": return cls(number) def seconds_because(self, reason: str) -> "Pause": - """Use seconds and provide a reason for the pause.""" + """Use seconds and provide a reason for the pause. + + Aliases: + * :meth:`~screenpy.actions.Pause.second_because` + """ self.unit = f"second{'s' if self.number != 1 else ''}" self.reason = self._massage_reason(reason) return self - second_because = seconds_because + def second_because(self, reason: str) -> "Pause": + """Alias for :meth:`~screenpy.actions.Pause.seconds_because`.""" + return self.seconds_because(reason) def milliseconds_because(self, reason: str) -> "Pause": """Use milliseconds and provide a reason for the pause.""" diff --git a/screenpy/actor.py b/screenpy/actor.py index 7356c3f0..c3d08892 100644 --- a/screenpy/actor.py +++ b/screenpy/actor.py @@ -13,6 +13,8 @@ from .protocols import Forgettable, Performable from .speech_tools import get_additive_description +# pylint: disable=too-many-public-methods + ENTRANCE_DIRECTIONS = [ "{actor} appears from behind the backdrop!", "{actor} arrives on stage!", @@ -56,16 +58,22 @@ def named(cls, name: Text) -> "Actor": return cls(name) def who_can(self, *abilities: Forgettable) -> "Actor": - """Add one or more Abilities to this Actor.""" + """Add one or more Abilities to this Actor. + + Aliases: + * :meth:`~screenpy.actor.Actor.can` + """ self.abilities.extend(abilities) return self - can = who_can + def can(self, *abilities: Forgettable) -> "Actor": + """Alias for :meth:`~screenpy.actor.Actor.who_can`.""" + return self.who_can(*abilities) def has_cleanup_tasks(self, *tasks: Performable) -> "Actor": """Assign one or more tasks to the Actor to perform when exiting.""" warnings.warn( - "This method is deprecated." + "This method is deprecated and will be removed in ScreenPy 4.2.0." " Please use either `has_ordered_cleanup_tasks`" " or `has_independent_cleanup_tasks` instead.", DeprecationWarning, @@ -78,12 +86,16 @@ def has_ordered_cleanup_tasks(self, *tasks: Performable) -> "Actor": The tasks given to this method must be performed successfully in order. If any task fails, any subsequent tasks will not be attempted and will be discarded. + + Aliases: + * :meth:`~screenpy.actor.Actor.with_ordered_cleanup_tasks` """ self.ordered_cleanup_tasks.extend(tasks) return self - has_cleanup_task = has_ordered_cleanup_tasks - with_cleanup_task = with_ordered_cleanup_tasks = has_ordered_cleanup_tasks + def with_ordered_cleanup_tasks(self, *tasks: Performable) -> "Actor": + """Alias for :meth:`~screenpy.actor.Actor.has_ordered_cleanup_tasks`.""" + return self.has_ordered_cleanup_tasks(*tasks) def has_independent_cleanup_tasks(self, *tasks: Performable) -> "Actor": """Assign one or more tasks for the Actor to perform when exiting. @@ -91,17 +103,25 @@ def has_independent_cleanup_tasks(self, *tasks: Performable) -> "Actor": The tasks included through this method are assumed to be independent; that is to say, all of them will be executed regardless of whether previous ones were successful. + + Aliases: + * :meth:`~screenpy.actor.Actor.with_independent_cleanup_tasks` """ self.independent_cleanup_tasks.extend(tasks) return self - with_independent_cleanup_tasks = has_independent_cleanup_tasks + def with_independent_cleanup_tasks(self, *tasks: Performable) -> "Actor": + """Alias for :meth:`~screenpy.actor.Actor.has_independent_cleanup_tasks`.""" + return self.has_independent_cleanup_tasks(*tasks) def uses_ability_to(self, ability: Type[T]) -> T: """Find the Ability referenced and return it, if the Actor is capable. Raises: UnableToPerform: the Actor doesn't possess the Ability. + + Aliases: + * :meth:`~screenpy.actor.Actor.ability_to` """ for a in self.abilities: if isinstance(a, ability): @@ -109,7 +129,9 @@ def uses_ability_to(self, ability: Type[T]) -> T: raise UnableToPerform(f"{self} does not have the Ability to {ability}") - ability_to = uses_ability_to + def ability_to(self, ability: Type[T]) -> T: + """Alias for :meth:`~screenpy.actor.Actor.uses_ability_to`.""" + return self.uses_ability_to(ability) def has_ability_to(self, ability: Type[Forgettable]) -> bool: """Ask whether the Actor has the Ability to do something.""" @@ -120,11 +142,22 @@ def has_ability_to(self, ability: Type[Forgettable]) -> bool: return False def attempts_to(self, *actions: Performable) -> None: - """Perform a list of Actions, one after the other.""" + """Perform a list of Actions, one after the other. + + Aliases: + * :meth:`~screenpy.actor.Actor.was_able_to` + * :meth:`~screenpy.actor.Actor.should` + """ for action in actions: self.perform(action) - was_able_to = should = attempts_to + def was_able_to(self, *actions: Performable) -> None: + """Alias for :meth:`~screenpy.actor.Actor.attempts_to`, for test setup.""" + return self.attempts_to(*actions) + + def should(self, *actions: Performable) -> None: + """Alias for :meth:`~screenpy.actor.Actor.attempts_to`, for test assertions.""" + return self.attempts_to(*actions) def perform(self, action: Performable) -> None: """Perform an Action.""" @@ -159,13 +192,29 @@ def cleans_up(self) -> None: self.cleans_up_ordered_tasks() def exit(self) -> None: - """Direct the Actor to forget all their Abilities.""" + """Direct the Actor to forget all their Abilities. + + Aliases: + * :meth:`~screenpy.actor.Actor.exit_stage_left` + * :meth:`~screenpy.actor.Actor.exit_stage_right` + * :meth:`~screenpy.actor.Actor.exit_through_vomitorium` + """ self.cleans_up() for ability in self.abilities: ability.forget() self.abilities = [] - exit_stage_left = exit_stage_right = exit_through_vomitorium = exit + def exit_stage_left(self) -> None: + """Alias for :meth:`~screenpy.actor.Actor.exit`.""" + return self.exit() + + def exit_stage_right(self) -> None: + """Alias for :meth:`~screenpy.actor.Actor.exit`.""" + return self.exit() + + def exit_through_vomitorium(self) -> None: + """Alias for :meth:`~screenpy.actor.Actor.exit`.""" + return self.exit() def __repr__(self) -> str: return self.name diff --git a/tests/conftest.py b/tests/conftest.py index c1b54d57..53aaf67d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -from typing import Callable, Generator, Any +from typing import Any, Callable, Generator from unittest import mock import pytest diff --git a/tests/test_actions.py b/tests/test_actions.py index 6dd28c11..6a4c3286 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -1,5 +1,6 @@ -from unittest import mock +import os import sys +from unittest import mock import pytest @@ -17,7 +18,6 @@ from screenpy.director import Director from screenpy.exceptions import DeliveryError, UnableToAct, UnableToDirect from screenpy.resolutions import IsEqualTo - from tests.conftest import mock_settings @@ -27,6 +27,15 @@ def test_can_be_instantiated(self): assert isinstance(atf, AttachTheFile) + def test_divines_filename(self): + filename = "thisisonlyatest.png" + filepath = os.sep.join(["this", "is", "a", "test", filename]) + atf_without_path = AttachTheFile(filename) + atf_with_path = AttachTheFile(filepath) + + assert atf_without_path.filename == filename + assert atf_with_path.filename == filename + @mock.patch("screenpy.actions.attach_the_file.the_narrator") def test_perform_attach_the_file_sends_kwargs(self, mocked_narrator, Tester): test_path = "souiiie.png" @@ -70,14 +79,22 @@ def get_mock_action(self, **kwargs): def test_can_be_instantiated(self): e1 = Eventually(None) - e2 = Eventually(None).trying_for(0).seconds() + e2 = Eventually(None).trying_for_no_longer_than(0).seconds() e3 = Eventually(None).trying_for(0).milliseconds() - e4 = Eventually(None).polling(0).seconds() + e4 = Eventually(None).for_(0).seconds() + e5 = Eventually(None).waiting_for(0).seconds() + e6 = Eventually(None).polling(0).seconds() + e7 = Eventually(None).polling_every(0).milliseconds() + e8 = Eventually(None).trying_every(0).seconds() assert isinstance(e1, Eventually) assert isinstance(e2, Eventually) assert isinstance(e3, Eventually) assert isinstance(e4, Eventually) + assert isinstance(e5, Eventually) + assert isinstance(e6, Eventually) + assert isinstance(e7, Eventually) + assert isinstance(e8, Eventually) def test_uses_timeframe_builder(self): ev = Eventually(None).trying_for(1) @@ -121,8 +138,10 @@ def test_valueerror_when_poll_is_larger_than_timeout(self, Tester): MockAction = self.get_mock_action() ev = ( Eventually(MockAction) - .polling_every(200).milliseconds() - .for_(100).milliseconds() + .polling_every(200) + .milliseconds() + .for_(100) + .milliseconds() ) with pytest.raises(ValueError) as actual_exception: @@ -186,7 +205,7 @@ def test_mentions_all_errors(self, mocked_time, Tester): class TestMakeNote: def test_can_be_instantiated(self): mn1 = MakeNote(None) - mn2 = MakeNote.of_the(None) + mn2 = MakeNote.of(None) mn3 = MakeNote.of_the(None).as_("") assert isinstance(mn1, MakeNote) diff --git a/tests/test_actor.py b/tests/test_actor.py index 8cb264be..9066a1db 100644 --- a/tests/test_actor.py +++ b/tests/test_actor.py @@ -18,7 +18,7 @@ def test_can_be_instantiated(): a1 = Actor.named("Tester") a2 = Actor.named("Tester").can(None) a3 = Actor.named("Tester").who_can(None) - a4 = Actor.named("Tester").who_can(None).with_cleanup_task(None) + a4 = Actor.named("Tester").who_can(None).with_ordered_cleanup_tasks(None) assert isinstance(a1, Actor) assert isinstance(a2, Actor) @@ -59,7 +59,7 @@ def test_find_abilities(): def test_performs_cleanup_tasks_when_exiting(): mocked_ordered_task = get_mock_task() mocked_independent_task = get_mock_task() - actor = Actor.named("Tester").with_cleanup_task(mocked_ordered_task) + actor = Actor.named("Tester").with_ordered_cleanup_tasks(mocked_ordered_task) actor.has_independent_cleanup_tasks(mocked_independent_task) actor.exit() @@ -87,9 +87,11 @@ def test_clears_cleanup_tasks(): mocked_task_with_exception.perform_as.side_effect = ValueError( "I will not buy this record, it is scratched." ) - actor1 = Actor.named("Tester").with_cleanup_task(mocked_task) + actor1 = Actor.named("Tester").with_ordered_cleanup_tasks(mocked_task) actor1.has_independent_cleanup_tasks(mocked_task) - actor2 = Actor.named("Tester").with_cleanup_task(mocked_task_with_exception) + actor2 = Actor.named("Tester").with_ordered_cleanup_tasks( + mocked_task_with_exception + ) actor2.has_independent_cleanup_tasks(mocked_task_with_exception) actor1.cleans_up() diff --git a/tests/test_adapters.py b/tests/test_adapters.py index 4488a06d..09e9c90d 100644 --- a/tests/test_adapters.py +++ b/tests/test_adapters.py @@ -1,6 +1,6 @@ import logging -from screenpy.narration.adapters.stdout_adapter import StdOutManager, StdOutAdapter +from screenpy.narration.adapters.stdout_adapter import StdOutAdapter, StdOutManager def prop(): diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 23604094..e07cb083 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -10,10 +10,10 @@ ContainsTheText, ContainsTheValue, DoesNot, - IsEmpty, Equal, HasLength, IsCloseTo, + IsEmpty, IsEqualTo, IsNot, ReadsExactly, @@ -21,7 +21,7 @@ def assert_matcher_annotation(obj: BaseResolution): - assert type(obj.matcher) is obj.__annotations__['matcher'] + assert type(obj.matcher) is obj.__annotations__["matcher"] class TestBaseResolution: @@ -40,6 +40,7 @@ def test_matcher_instantiation(self, args, kwargs, expected): class MockResolution(BaseResolution): """Must be defined here for new mock matchers.""" + matcher_function = mock.Mock() resolution = MockResolution(*args, **kwargs) @@ -54,12 +55,12 @@ class MockResolution(BaseResolution): ("describe_to", [mock.Mock()], "describe_to"), ("describe_match", [mock.Mock(), mock.Mock()], "describe_match"), ("describe_mismatch", [mock.Mock(), mock.Mock()], "describe_mismatch"), - ] + ], ) def test_passthroughs(self, method, args, expected_method): - class MockResolution(BaseResolution): """Must be defined here for new mock matchers.""" + matcher_function = mock.Mock() resolution = MockResolution() @@ -69,9 +70,9 @@ class MockResolution(BaseResolution): getattr(resolution.matcher, expected_method).assert_called_once_with(*args) def test___repr__(self): - class MockResolution(BaseResolution): """Must be defined here for new mock matchers.""" + matcher_function = mock.Mock() get_line = mock.Mock(return_value="")