diff --git a/screenpy/actions/attach_the_file.py b/screenpy/actions/attach_the_file.py index 7d61909..4cbc513 100644 --- a/screenpy/actions/attach_the_file.py +++ b/screenpy/actions/attach_the_file.py @@ -22,6 +22,11 @@ class AttachTheFile: ) """ + @property + def filename(self) -> str: + """Get the filename from the filepath.""" + return os.path.basename(self.filepath) + def describe(self) -> str: """Describe the Action in present tense.""" return f"Attach a file named {self.filename}." @@ -34,5 +39,4 @@ def perform_as(self, _: Actor) -> None: # ANN401 ignored here to allow for new adapters to use any kwargs. def __init__(self, filepath: str, **kwargs: Any) -> None: # noqa: ANN401 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 4a44a91..cd2671a 100644 --- a/screenpy/actions/eventually.py +++ b/screenpy/actions/eventually.py @@ -117,6 +117,11 @@ def trying_every(self, amount: float) -> _TimeframeBuilder: """Alias for :meth:`~screenpy.actions.Eventually.polling`.""" return self.polling(amount) + @property + def performable_to_log(self) -> str: + """Get a log-friendly description of the performable.""" + return get_additive_description(self.performable) + def describe(self) -> str: """Describe the Action in present tense.""" return f"Eventually {self.performable_to_log}." @@ -159,7 +164,6 @@ def perform_as(self, the_actor: Actor) -> None: def __init__(self, performable: Performable) -> None: self.performable = performable - self.performable_to_log = get_additive_description(self.performable) self.caught_error = None self.unique_errors: list[BaseException] = [] self.timeout = settings.TIMEOUT diff --git a/screenpy/actions/log.py b/screenpy/actions/log.py index bb530ba..e3eb8b1 100644 --- a/screenpy/actions/log.py +++ b/screenpy/actions/log.py @@ -33,6 +33,11 @@ def the(cls, question: T_Q) -> Self: """Supply the Question to answer.""" return cls(question) + @property + def question_to_log(self) -> str: + """Get a log-friendly description of the Question.""" + return get_additive_description(self.question) + @beat("{} examines {question_to_log}.") def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to announce the answer to the Question.""" @@ -44,4 +49,3 @@ def perform_as(self, the_actor: Actor) -> None: def __init__(self, question: T_Q) -> None: self.question = question - self.question_to_log = get_additive_description(self.question) diff --git a/screenpy/actions/make_note.py b/screenpy/actions/make_note.py index 10ecc2f..12356bc 100644 --- a/screenpy/actions/make_note.py +++ b/screenpy/actions/make_note.py @@ -37,7 +37,6 @@ class MakeNote: """ key: str | None - key_to_log: str | None question: T_Q @classmethod @@ -57,12 +56,16 @@ def of_the(cls, question: T_Q) -> Self: def as_(self, key: str) -> Self: """Set the key to use to recall this noted value.""" self.key = key - self.key_to_log = represent_prop(key) return self + @property + def key_to_log(self) -> str | None: + """Get a proper representation of the key.""" + return represent_prop(self.key) + def describe(self) -> str: """Describe the Action in present tense.""" - return f"Make a note under {represent_prop(self.key)}." + return f"Make a note under {self.key_to_log}." @beat("{} jots something down under {key_to_log}.") def perform_as(self, the_actor: Actor) -> None: @@ -90,4 +93,3 @@ def __init__( ) -> None: self.question = question self.key = key - self.key_to_log = represent_prop(key) diff --git a/screenpy/resolutions/contains_the_entry.py b/screenpy/resolutions/contains_the_entry.py index 67e6691..b6ed174 100644 --- a/screenpy/resolutions/contains_the_entry.py +++ b/screenpy/resolutions/contains_the_entry.py @@ -37,6 +37,18 @@ class ContainsTheEntry: the_actor.should(See.the(MathTestAnswers(), ContainsTheEntry("Problem3", 45))) """ + @property + def entry_plural(self) -> str: + """Decide if we need "entry" or "entries" in the beat message.""" + return "entries" if len(self.entries) != 1 else "entry" + + @property + def entries_to_log(self) -> str: + """Represent the entries in a log-friendly way.""" + return ", ".join( + f"{represent_prop(k)}->{represent_prop(v)}" for k, v in self.entries.items() + ) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"A mapping with the {self.entry_plural} {self.entries_to_log}." @@ -77,7 +89,3 @@ def __init__(self, *kv_args: Any, **kv_kwargs: Any) -> None: (kv_args[i], kv_args[i + 1]) for i in range(0, len(kv_args), 2) ] self.entries = dict(pairs, **kv_kwargs) - self.entry_plural = "entries" if len(self.entries) != 1 else "entry" - self.entries_to_log = ", ".join( - f"{represent_prop(k)}->{represent_prop(v)}" for k, v in self.entries.items() - ) diff --git a/screenpy/resolutions/contains_the_item.py b/screenpy/resolutions/contains_the_item.py index ecd8e9b..5e47917 100644 --- a/screenpy/resolutions/contains_the_item.py +++ b/screenpy/resolutions/contains_the_item.py @@ -1,13 +1,17 @@ """Matches a list that contains the desired item.""" -from typing import Generic, Sequence, TypeVar +from __future__ import annotations + +from typing import TYPE_CHECKING, Generic, Sequence, TypeVar from hamcrest import has_item -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + T = TypeVar("T") @@ -21,6 +25,11 @@ class ContainsTheItem(Generic[T]): ) """ + @property + def item_to_log(self) -> str | T: + """Represent the item in a log-friendly way.""" + return represent_prop(self.item) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"A sequence containing {self.item_to_log}." @@ -32,4 +41,3 @@ def resolve(self) -> Matcher[Sequence[T]]: def __init__(self, item: T) -> None: self.item = item - self.item_to_log = represent_prop(item) diff --git a/screenpy/resolutions/contains_the_key.py b/screenpy/resolutions/contains_the_key.py index a83998b..e8760cc 100644 --- a/screenpy/resolutions/contains_the_key.py +++ b/screenpy/resolutions/contains_the_key.py @@ -1,13 +1,17 @@ """Matches a dictionary that contains the desired key.""" -from typing import Any, Generic, Hashable, Mapping, TypeVar +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Generic, Hashable, Mapping, TypeVar from hamcrest import has_key -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + K = TypeVar("K", bound=Hashable) @@ -19,6 +23,11 @@ class ContainsTheKey(Generic[K]): the_actor.should(See.the(LastResponseBody(), ContainsTheKey("skeleton"))) """ + @property + def key_to_log(self) -> str | K: + """Represent the key in a log-friendly way.""" + return represent_prop(self.key) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Containing the key {self.key_to_log}." @@ -30,4 +39,3 @@ def resolve(self) -> Matcher[Mapping[K, Any]]: def __init__(self, key: K) -> None: self.key = key - self.key_to_log = represent_prop(key) diff --git a/screenpy/resolutions/contains_the_text.py b/screenpy/resolutions/contains_the_text.py index 5ddf402..c40682c 100644 --- a/screenpy/resolutions/contains_the_text.py +++ b/screenpy/resolutions/contains_the_text.py @@ -17,6 +17,11 @@ class ContainsTheText: ) """ + @property + def text_to_log(self) -> str: + """Represent the text in a log-friendly way.""" + return represent_prop(self.text) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Containing the text {self.text_to_log}." @@ -28,4 +33,3 @@ def resolve(self) -> Matcher[str]: def __init__(self, text: str) -> None: self.text = text - self.text_to_log = represent_prop(text) diff --git a/screenpy/resolutions/contains_the_value.py b/screenpy/resolutions/contains_the_value.py index c6c8198..9e97ad4 100644 --- a/screenpy/resolutions/contains_the_value.py +++ b/screenpy/resolutions/contains_the_value.py @@ -1,13 +1,17 @@ """Matches a dictionary that contains a specific value.""" -from typing import Any, Generic, Mapping, TypeVar +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Generic, Mapping, TypeVar from hamcrest import has_value -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + V = TypeVar("V") @@ -21,6 +25,11 @@ class ContainsTheValue(Generic[V]): ) """ + @property + def value_to_log(self) -> str | V: + """Represent the value in a log-friendly way.""" + return represent_prop(self.value) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Containing the value {self.value_to_log}." @@ -32,4 +41,3 @@ def resolve(self) -> Matcher[Mapping[Any, V]]: def __init__(self, value: V) -> None: self.value = value - self.value_to_log = represent_prop(value) diff --git a/screenpy/resolutions/ends_with.py b/screenpy/resolutions/ends_with.py index e1d2ef7..94c39f3 100644 --- a/screenpy/resolutions/ends_with.py +++ b/screenpy/resolutions/ends_with.py @@ -17,6 +17,11 @@ class EndsWith: ) """ + @property + def postfix_to_log(self) -> str: + """Represent the postfix in a log-friendly way.""" + return represent_prop(self.postfix) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Ending with {self.postfix_to_log}." @@ -28,4 +33,3 @@ def resolve(self) -> Matcher[str]: def __init__(self, postfix: str) -> None: self.postfix = postfix - self.postfix_to_log = represent_prop(postfix) diff --git a/screenpy/resolutions/has_length.py b/screenpy/resolutions/has_length.py index 3b2e53e..8466876 100644 --- a/screenpy/resolutions/has_length.py +++ b/screenpy/resolutions/has_length.py @@ -18,15 +18,19 @@ class HasLength: ) """ + @property + def item_plural(self) -> str: + """Decide if we need "item" or "items" in the beat message.""" + return "items" if self.length != 1 else "item" + def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"{self.length} item{self.plural} long." + return f"{self.length} {self.item_plural} long." - @beat("... hoping it's a collection with {length} item{plural} in it.") + @beat("... hoping it's a collection with {length} {item_plural} in it.") def resolve(self) -> Matcher[Sized]: """Produce the Matcher to make the assertion.""" return has_length(self.length) def __init__(self, length: int) -> None: self.length = length - self.plural = "s" if self.length != 1 else "" diff --git a/screenpy/resolutions/is_equal_to.py b/screenpy/resolutions/is_equal_to.py index f5c2b39..df3a873 100644 --- a/screenpy/resolutions/is_equal_to.py +++ b/screenpy/resolutions/is_equal_to.py @@ -1,13 +1,17 @@ """Matches using equality.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any from hamcrest import equal_to -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + class IsEqualTo: """Match on an equal object. @@ -19,6 +23,11 @@ class IsEqualTo: ) """ + @property + def expected_to_log(self) -> str | object: + """Represent the expected object in a log-friendly way.""" + return represent_prop(self.expected) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Equal to {self.expected_to_log}." @@ -30,4 +39,3 @@ def resolve(self) -> Matcher[Any]: def __init__(self, obj: object) -> None: self.expected = obj - self.expected_to_log = represent_prop(obj) diff --git a/screenpy/resolutions/is_greater_than.py b/screenpy/resolutions/is_greater_than.py index 8fa7e56..1cf15ce 100644 --- a/screenpy/resolutions/is_greater_than.py +++ b/screenpy/resolutions/is_greater_than.py @@ -1,13 +1,17 @@ """Matches a value greater than the given number.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any from hamcrest import greater_than -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + class IsGreaterThan: """Match on a number that is greater than the given number. @@ -17,6 +21,11 @@ class IsGreaterThan: the_actor.should(See.the(Number.of(COUPONS), IsGreaterThan(1))) """ + @property + def number_to_log(self) -> str | float: + """Represent the number in a log-friendly way.""" + return represent_prop(self.number) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Greater than {self.number_to_log}." @@ -28,4 +37,3 @@ def resolve(self) -> Matcher[Any]: def __init__(self, number: float) -> None: self.number = number - self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_greater_than_or_equal_to.py b/screenpy/resolutions/is_greater_than_or_equal_to.py index f10ee85..b36d738 100644 --- a/screenpy/resolutions/is_greater_than_or_equal_to.py +++ b/screenpy/resolutions/is_greater_than_or_equal_to.py @@ -1,13 +1,17 @@ """Matches a value greater than the given number.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any from hamcrest import greater_than_or_equal_to -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + class IsGreaterThanOrEqualTo: """Match on a number that is greater than or equal to the given number. @@ -19,6 +23,11 @@ class IsGreaterThanOrEqualTo: ) """ + @property + def number_to_log(self) -> str | float: + """Represent the number in a log-friendly way.""" + return represent_prop(self.number) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Greater than or equal to {self.number_to_log}." @@ -30,4 +39,3 @@ def resolve(self) -> Matcher[Any]: def __init__(self, number: float) -> None: self.number = number - self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_in_range.py b/screenpy/resolutions/is_in_range.py index 8edc68f..1998e20 100644 --- a/screenpy/resolutions/is_in_range.py +++ b/screenpy/resolutions/is_in_range.py @@ -6,6 +6,7 @@ from screenpy.exceptions import UnableToFormResolution from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop from .custom_matchers.is_in_bounds import is_in_bounds @@ -34,11 +35,20 @@ class IsInRange: the_actor.should(See.the(Number.of(COOKIES), IsInRange("[1, 5)"))) """ + @property + def bounds_to_log(self) -> str | int: + """Represent the bounds in a log-friendly way.""" + bounding_string = self.bounds[0] # given bounding string + if len(self.bounds) == 2: # noqa: PLR2004 + # given bounding numbers + bounding_string = f"[{self.bounds[0]}, {self.bounds[1]}]" + return represent_prop(bounding_string) + def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"In the range {self.bounding_string}." + return f"In the range {self.bounds_to_log}." - @beat("... hoping it's in the range {bounding_string}.") + @beat("... hoping it's in the range {bounds_to_log}.") def resolve(self) -> Matcher[float]: """Produce the Matcher to make the assertion.""" return is_in_bounds(*self.bounds) @@ -47,9 +57,4 @@ def __init__(self, *bounds: int | str) -> None: if len(bounds) > 2: # noqa: PLR2004 msg = f"{self.__class__.__name__} was given too many arguments: {bounds}." raise UnableToFormResolution(msg) - self.bounds = bounds - self.bounding_string = self.bounds[0] # given bounding string - if len(self.bounds) == 2: # noqa: PLR2004 - # given bounding numbers - self.bounding_string = f"[{self.bounds[0]}, {self.bounds[1]}]" diff --git a/screenpy/resolutions/is_less_than.py b/screenpy/resolutions/is_less_than.py index 452ec74..a4ed41e 100644 --- a/screenpy/resolutions/is_less_than.py +++ b/screenpy/resolutions/is_less_than.py @@ -1,11 +1,17 @@ """Matches a value less than the given number.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + from hamcrest import less_than -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + class IsLessThan: """Match on a number that is less than the given number. @@ -17,6 +23,11 @@ class IsLessThan: ) """ + @property + def number_to_log(self) -> str | float: + """Represent the number in a log-friendly way.""" + return represent_prop(self.number) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Less than {self.number_to_log}." @@ -28,4 +39,3 @@ def resolve(self) -> Matcher[float]: def __init__(self, number: float) -> None: self.number = number - self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_less_than_or_equal_to.py b/screenpy/resolutions/is_less_than_or_equal_to.py index 887cb19..bee4f7d 100644 --- a/screenpy/resolutions/is_less_than_or_equal_to.py +++ b/screenpy/resolutions/is_less_than_or_equal_to.py @@ -1,11 +1,17 @@ """Matches a value less than or equal to the given number.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + from hamcrest import less_than_or_equal_to -from hamcrest.core.matcher import Matcher from screenpy.pacing import beat from screenpy.speech_tools import represent_prop +if TYPE_CHECKING: + from hamcrest.core.matcher import Matcher + class IsLessThanOrEqualTo: """Match on a number that is less than or equal to the given number. @@ -17,6 +23,11 @@ class IsLessThanOrEqualTo: ) """ + @property + def number_to_log(self) -> str | float: + """Represent the number in a log-friendly way.""" + return represent_prop(self.number) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Less than or equal to {self.number_to_log}." @@ -28,4 +39,3 @@ def resolve(self) -> Matcher[float]: def __init__(self, number: float) -> None: self.number = number - self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_not.py b/screenpy/resolutions/is_not.py index 18d3319..03d21ad 100644 --- a/screenpy/resolutions/is_not.py +++ b/screenpy/resolutions/is_not.py @@ -20,6 +20,11 @@ class IsNot: the_actor.should(See.the(Element(WELCOME_BANNER), IsNot(Visible()))) """ + @property + def resolution_to_log(self) -> str: + """Represent the Resolution in a log-friendly way.""" + return get_additive_description(self.resolution) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Not {self.resolution_to_log}." @@ -31,4 +36,3 @@ def resolve(self) -> Matcher[object]: def __init__(self, resolution: Resolvable) -> None: self.resolution = resolution - self.resolution_to_log = get_additive_description(self.resolution) diff --git a/screenpy/resolutions/reads_exactly.py b/screenpy/resolutions/reads_exactly.py index cfeff37..236359e 100644 --- a/screenpy/resolutions/reads_exactly.py +++ b/screenpy/resolutions/reads_exactly.py @@ -17,6 +17,11 @@ class ReadsExactly: ) """ + @property + def text_to_log(self) -> str: + """Represent the text in a log-friendly way.""" + return represent_prop(self.text) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"{self.text_to_log}, verbatim." @@ -28,4 +33,3 @@ def resolve(self) -> Matcher[object]: def __init__(self, text: str) -> None: self.text = text - self.text_to_log = represent_prop(text) diff --git a/screenpy/resolutions/starts_with.py b/screenpy/resolutions/starts_with.py index 71a35b0..a39de18 100644 --- a/screenpy/resolutions/starts_with.py +++ b/screenpy/resolutions/starts_with.py @@ -17,6 +17,11 @@ class StartsWith: ) """ + @property + def prefix_to_log(self) -> str: + """Represent the prefix in a log-friendly way.""" + return represent_prop(self.prefix) + def describe(self) -> str: """Describe the Resolution's expectation.""" return f"Starting with {self.prefix_to_log}." @@ -28,4 +33,3 @@ def resolve(self) -> Matcher[str]: def __init__(self, prefix: str) -> None: self.prefix = prefix - self.prefix_to_log = represent_prop(prefix)