From bb4ea34b2681b0d5cfe8cb06f8f1b38cd9facb54 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 12 Oct 2023 13:49:16 -0500 Subject: [PATCH] Beat `None` handling (#105) * Adding test to show what should happen for #101 * Adding ability to differentiate between certain types of functions. * removing inheritance from test example question --- screenpy/pacing.py | 15 ++++++-- tests/test_pacing.py | 81 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/screenpy/pacing.py b/screenpy/pacing.py index 1e545ff..25a6998 100644 --- a/screenpy/pacing.py +++ b/screenpy/pacing.py @@ -4,7 +4,6 @@ (cases), and provide the gravitas (severity) of those groupings; or markers for moments the Narrator should narrate. """ - import re from functools import wraps from typing import Any, Callable, Optional @@ -13,9 +12,21 @@ from screenpy.speech_tools import represent_prop Function = Callable[..., Any] + the_narrator: Narrator = Narrator(adapters=[StdOutAdapter()]) +def function_should_log_none(func: Function) -> bool: + """Helper function to decide when to log return values. + + Determine if function is attached to one of the protocols that allow for anything + to return. + """ + if func.__annotations__ and "return" in func.__annotations__: + return func.__annotations__["return"] is not None + return False + + def act(title: str, gravitas: Optional[str] = None) -> Callable[[Function], Function]: """Decorator to mark an "act". @@ -89,7 +100,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: completed_line = f"{line.format(actor, **cues)}" with the_narrator.stating_a_beat(func, completed_line, gravitas) as n_func: retval = n_func(*args, **kwargs) - if retval is not None: + if retval is not None or function_should_log_none(func): aside(f"=> {represent_prop(retval)}") return retval diff --git a/tests/test_pacing.py b/tests/test_pacing.py index 42c52e4..56c563f 100644 --- a/tests/test_pacing.py +++ b/tests/test_pacing.py @@ -1,4 +1,7 @@ -from screenpy import act, aside, beat, scene +import logging +from typing import Optional + +from screenpy import Actor, IsEqualTo, See, act, aside, beat, scene def prop(): @@ -18,6 +21,52 @@ def use(self): pass +class NonesyQuestion: + @beat("{} examines NonesyQuestion") + def answered_by(self, _: Actor) -> object: + return None + + def describe(self) -> str: + return "NonesyQuestion" + + +class CornerCase: + @beat("{} examines CornerCase") + def answered_by(self, _: Actor) -> object: + self.do_not_log_me_man() + self.does_return_something() + self.no_annotations_rt_none(False) + self.no_annotations_rt_int(True) + return None + + @beat("Blah!") + def do_not_log_me_man(self) -> None: + return None + + @beat("Foobar...") + def does_return_something(self, toggle: bool = False) -> Optional[int]: + if toggle: + return 1 + return None + + # purposfully not annotated + @beat("Baz?") + def no_annotations_rt_none(self, toggle=False): + if toggle: + return 1 + return None + + # purposfully not annotated + @beat("Bazinga!!") + def no_annotations_rt_int(self, toggle=False): + if toggle: + return 1 + return None + + def describe(self) -> str: + return "CornerCase" + + class TestAct: def test_calls_narrators_method(self, mocked_narrator) -> None: test_act = "Test Act" @@ -55,6 +104,36 @@ def test_interpolations(self, mocked_narrator) -> None: completed_line = mocked_narrator.stating_a_beat.call_args_list[0][0][1] assert completed_line == f"The {test_weapon} in the {test_room}!" + def test_beat_logging_none(self, Tester, caplog): + caplog.set_level(logging.INFO) + See(NonesyQuestion(), IsEqualTo(None)).perform_as(Tester) + + assert [r.msg for r in caplog.records] == [ + "Tester sees if nonesyQuestion is equal to .", + " Tester examines NonesyQuestion", + " => ", + " ... hoping it's equal to .", + " => ", + ] + + def test_beat_logging_none_corner(self, Tester, caplog): + caplog.set_level(logging.INFO) + See(CornerCase(), IsEqualTo(None)).perform_as(Tester) + + assert [r.msg for r in caplog.records] == [ + "Tester sees if cornerCase is equal to .", + " Tester examines CornerCase", + " Blah!", + " Foobar...", + " => ", + " Baz?", + " Bazinga!!", + " => <1>", + " => ", + " ... hoping it's equal to .", + " => ", + ] + class TestAside: def test_calls_narrators_method(self, mocked_narrator) -> None: