Skip to content

Commit

Permalink
ScreenPyHQ#25 subclassing support for mypy prior to PEP 673 and pytho…
Browse files Browse the repository at this point in the history
  • Loading branch information
bandophahita committed Sep 22, 2022
1 parent 89c20ea commit a7f4686
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 58 deletions.
16 changes: 9 additions & 7 deletions screenpy/actions/make_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
Make a quick note about the answer to a Question.
"""

from typing import Any, Optional, Union
from typing import Any, Optional, Type, TypeVar, Union

from screenpy import Actor, Director
from screenpy.exceptions import UnableToAct
from screenpy.pacing import aside, beat
from screenpy.protocols import Answerable, ErrorKeeper

Self = TypeVar("Self", bound="MakeNote")


class MakeNote:
"""Make a note of a value or the answer to a Question.
Expand All @@ -29,7 +31,7 @@ class MakeNote:
key: Optional[str]

@classmethod
def of(cls, question: Union[Answerable, Any]) -> "MakeNote":
def of(cls: Type[Self], question: Union[Answerable, Any]) -> Self:
"""Supply the Question to answer and its arguments.
Aliases:
Expand All @@ -38,21 +40,21 @@ def of(cls, question: Union[Answerable, Any]) -> "MakeNote":
return cls(question)

@classmethod
def of_the(cls, question: Union[Answerable, Any]) -> "MakeNote":
def of_the(cls: Type[Self], question: Union[Answerable, Any]) -> Self:
"""Alias for :meth:`~screenpy.actions.MakeNote.of`."""
return cls.of(question)

def as_(self, key: str) -> "MakeNote":
def as_(self: Self, key: str) -> Self:
"""Set the key to use to recall this noted value."""
self.key = key
return self

def describe(self) -> str:
def describe(self: Self) -> str:
"""Describe the Action in present tense."""
return f"Make a note under {self.key}."

@beat('{} jots something down under "{key}".')
def perform_as(self, the_actor: Actor) -> None:
def perform_as(self: Self, the_actor: Actor) -> None:
"""Direct the Actor to take a note."""
if self.key is None:
raise UnableToAct("No key was provided to name this note.")
Expand All @@ -70,7 +72,7 @@ def perform_as(self, the_actor: Actor) -> None:
Director().notes(self.key, value)

def __init__(
self, question: Union[Answerable, Any], key: Optional[str] = None
self: Self, question: Union[Answerable, Any], key: Optional[str] = None
) -> None:
self.question = question
self.key = key
18 changes: 9 additions & 9 deletions screenpy/actions/pause.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from screenpy.exceptions import UnableToAct
from screenpy.pacing import beat

T_pause = TypeVar("T_pause", bound="Pause")
Self = TypeVar("Self", bound="Pause")


class Pause:
Expand All @@ -36,11 +36,11 @@ class Pause:
time: float

@classmethod
def for_(cls: Type[T_pause], number: float) -> T_pause:
def for_(cls: Type[Self], number: float) -> Self:
"""Specify how many seconds or milliseconds to wait for."""
return cls(number)

def seconds_because(self: T_pause, reason: str) -> T_pause:
def seconds_because(self: Self, reason: str) -> Self:
"""Use seconds and provide a reason for the pause.
Aliases:
Expand All @@ -50,23 +50,23 @@ def seconds_because(self: T_pause, reason: str) -> T_pause:
self.reason = self._massage_reason(reason)
return self

def second_because(self: T_pause, reason: str) -> T_pause:
def second_because(self: Self, reason: str) -> Self:
"""Alias for :meth:`~screenpy.actions.Pause.seconds_because`."""
return self.seconds_because(reason)

def milliseconds_because(self: T_pause, reason: str) -> T_pause:
def milliseconds_because(self: Self, reason: str) -> Self:
"""Use milliseconds and provide a reason for the pause."""
self.unit = f"millisecond{'s' if self.number != 1 else ''}"
self.time = self.time / 1000.0
self.reason = self._massage_reason(reason)
return self

def describe(self: T_pause) -> str:
def describe(self: Self) -> str:
"""Describe the Action in present tense."""
return f"Pause for {self.number} {self.unit} {self.reason}."

@beat("{} pauses for {number} {unit} {reason}.")
def perform_as(self: T_pause, _: Actor) -> None:
def perform_as(self: Self, _: Actor) -> None:
"""Direct the Actor to take their union-mandated break."""
if not self.reason:
raise UnableToAct(
Expand All @@ -76,7 +76,7 @@ def perform_as(self: T_pause, _: Actor) -> None:

sleep(self.time)

def _massage_reason(self: T_pause, reason: str) -> str:
def _massage_reason(self: Self, reason: str) -> str:
"""Apply some gentle massaging to the reason string."""
if not reason.startswith("because"):
reason = f"because {reason}"
Expand All @@ -85,7 +85,7 @@ def _massage_reason(self: T_pause, reason: str) -> str:

return reason

def __init__(self: T_pause, number: float) -> None:
def __init__(self: Self, number: float) -> None:
self.number = number
self.time = number
self.unit = f"second{'s' if self.number != 1 else ''}"
Expand Down
14 changes: 9 additions & 5 deletions screenpy/actions/see.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Make an assertion using a Question and a Resolution.
"""

from typing import Any, Union
from typing import Any, Type, TypeVar, Union

from hamcrest import assert_that

Expand All @@ -12,6 +12,8 @@
from screenpy.resolutions import BaseResolution
from screenpy.speech_tools import get_additive_description

Self = TypeVar("Self", bound="See")


class See:
"""See if a value or the answer to a Question matches the Resolution.
Expand All @@ -32,16 +34,18 @@ class See:
"""

@classmethod
def the(cls, question: Union[Answerable, Any], resolution: BaseResolution) -> "See":
def the(
cls: Type[Self], question: Union[Answerable, Any], resolution: BaseResolution
) -> Self:
"""Supply the Question (or value) and Resolution to test."""
return cls(question, resolution)

def describe(self) -> str:
def describe(self: Self) -> str:
"""Describe the Action in present tense."""
return f"See if {self.question_to_log} is {self.resolution_to_log}."

@beat("{} sees if {question_to_log} is {resolution_to_log}.")
def perform_as(self, the_actor: Actor) -> None:
def perform_as(self: Self, the_actor: Actor) -> None:
"""Direct the Actor to make an observation."""
if isinstance(self.question, Answerable):
value: object = self.question.answered_by(the_actor)
Expand All @@ -57,7 +61,7 @@ def perform_as(self, the_actor: Actor) -> None:
assert_that(value, self.resolution, reason)

def __init__(
self, question: Union[Answerable, Any], resolution: BaseResolution
self: Self, question: Union[Answerable, Any], resolution: BaseResolution
) -> None:
self.question = question
self.question_to_log = get_additive_description(question)
Expand Down
12 changes: 7 additions & 5 deletions screenpy/actions/see_all_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
all of which are expected to be true.
"""

from typing import Tuple
from typing import Tuple, Type, TypeVar

from screenpy import Actor
from screenpy.exceptions import UnableToAct
Expand All @@ -13,6 +13,8 @@

from .see import See

Self = TypeVar("Self", bound="SeeAllOf")


class SeeAllOf:
"""See if all the provided values or Questions match their Resolutions.
Expand All @@ -38,21 +40,21 @@ class SeeAllOf:
"""

@classmethod
def the(cls, *tests: Tuple[Answerable, BaseResolution]) -> "SeeAllOf":
def the(cls: Type[Self], *tests: Tuple[Answerable, BaseResolution]) -> Self:
"""Supply any number of Question/value + Resolution tuples to test."""
return cls(*tests)

def describe(self) -> str:
def describe(self: Self) -> str:
"""Describe the Action in present tense."""
return f"See if all of {self.number_of_tests} tests pass."

@beat("{} sees if all of the following {number_of_tests} tests pass:")
def perform_as(self, the_actor: Actor) -> None:
def perform_as(self: Self, the_actor: Actor) -> None:
"""Direct the Actor to make a series of observations."""
for question, resolution in self.tests:
the_actor.should(See.the(question, resolution))

def __init__(self, *tests: Tuple[Answerable, BaseResolution]) -> None:
def __init__(self: Self, *tests: Tuple[Answerable, BaseResolution]) -> None:
if len(tests) < 2:
raise UnableToAct(
"Must supply 2 or more tests for SeeAllOf."
Expand Down
12 changes: 7 additions & 5 deletions screenpy/actions/see_any_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
at least one of which is expected to be true.
"""

from typing import Tuple
from typing import Tuple, Type, TypeVar

from screenpy import Actor
from screenpy.exceptions import UnableToAct
Expand All @@ -13,6 +13,8 @@

from .see import See

Self = TypeVar("Self", bound="SeeAnyOf")


class SeeAnyOf:
"""See if at least one value or Question matches its Resolution.
Expand All @@ -39,16 +41,16 @@ class SeeAnyOf:
"""

@classmethod
def the(cls, *tests: Tuple[Answerable, BaseResolution]) -> "SeeAnyOf":
def the(cls: Type[Self], *tests: Tuple[Answerable, BaseResolution]) -> Self:
"""Supply any number of Question/value + Resolution tuples to test."""
return cls(*tests)

def describe(self) -> str:
def describe(self: Self) -> str:
"""Describe the Action in present tense."""
return f"See if any of {self.number_of_tests} tests pass."

@beat("{} sees if any of the following {number_of_tests} tests pass:")
def perform_as(self, the_actor: Actor) -> None:
def perform_as(self: Self, the_actor: Actor) -> None:
"""Direct the Actor to make a series of observations."""
none_passed = True
for question, resolution in self.tests:
Expand All @@ -61,7 +63,7 @@ def perform_as(self, the_actor: Actor) -> None:
if none_passed:
raise AssertionError(f"{the_actor} did not find any expected answers!")

def __init__(self, *tests: Tuple[Answerable, BaseResolution]) -> None:
def __init__(self: Self, *tests: Tuple[Answerable, BaseResolution]) -> None:
if len(tests) < 2:
raise UnableToAct(
"Must supply 2 or more tests for SeeAnyOf."
Expand Down
Loading

0 comments on commit a7f4686

Please sign in to comment.