diff --git a/pyproject.toml b/pyproject.toml index cb27460..84ef9c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,6 +177,17 @@ ignore = [ "ANN102", # missing cls annotation, we only annotate cls when we return it. ] +extend-safe-fixes = [ + "EM101", + "EM102", + "TCH001", "TCH002", "TCH003", "TCH004", + "C419", + "D200", "D205", "D415", + "PT003", "PT006", "PT018", + "RET504", + "UP006", "UP007", +] + [tool.ruff.lint.per-file-ignores] "tests/**" = [ "D", # we don't need public-API-polished docstrings in tests. @@ -206,6 +217,10 @@ ignore-overlong-task-comments = true convention = "google" +[tool.ruff.lint.flake8-type-checking] +strict = true + + [build-system] requires = ["poetry-core>=1.2.0"] build-backend = "poetry.core.masonry.api" diff --git a/screenpy/actions/either.py b/screenpy/actions/either.py index ad19b12..b6f556b 100644 --- a/screenpy/actions/either.py +++ b/screenpy/actions/either.py @@ -12,6 +12,8 @@ from screenpy.speech_tools import get_additive_description if TYPE_CHECKING: + from typing_extensions import Self + from screenpy import Actor from screenpy.protocols import Performable @@ -62,7 +64,7 @@ def perform_as(self, the_actor: Actor) -> None: the_actor.will(*self.except_performables) return - def or_(self, *except_performables: Performable) -> Either: + def or_(self, *except_performables: Performable) -> Self: """Provide the alternative routine to perform. Aliases: @@ -77,7 +79,7 @@ def or_(self, *except_performables: Performable) -> Either: except_ = else_ = otherwise = alternatively = failing_that = or_ - def ignoring(self, *ignored_exceptions: type[BaseException]) -> Either: + def ignoring(self, *ignored_exceptions: type[BaseException]) -> Self: """Set the expception classes to ignore.""" self.ignore_exceptions = ignored_exceptions return self diff --git a/screenpy/actions/log.py b/screenpy/actions/log.py index 960e87c..bb530ba 100644 --- a/screenpy/actions/log.py +++ b/screenpy/actions/log.py @@ -2,21 +2,20 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import aside, beat from screenpy.protocols import Answerable from screenpy.speech_tools import get_additive_description, represent_prop if TYPE_CHECKING: + from typing_extensions import Self + from screenpy.actor import Actor from .see import T_Q -SelfLog = TypeVar("SelfLog", bound="Log") - - class Log: """Log the answer to a Question, or anything. @@ -30,7 +29,7 @@ class Log: """ @classmethod - def the(cls: type[SelfLog], question: T_Q) -> SelfLog: + def the(cls, question: T_Q) -> Self: """Supply the Question to answer.""" return cls(question) diff --git a/screenpy/actions/make_note.py b/screenpy/actions/make_note.py index 4105a79..2a57a64 100644 --- a/screenpy/actions/make_note.py +++ b/screenpy/actions/make_note.py @@ -11,11 +11,12 @@ from screenpy.speech_tools import represent_prop if TYPE_CHECKING: - from typing import TypeVar, Union + from typing import Union + + from typing_extensions import Self from screenpy.actor import Actor - SelfMakeNote = TypeVar("SelfMakeNote", bound="MakeNote") T_Q = Union[Answerable, object] @@ -39,7 +40,7 @@ class MakeNote: question: T_Q @classmethod - def of(cls: type[SelfMakeNote], question: T_Q) -> SelfMakeNote: + def of(cls, question: T_Q) -> Self: """Supply the Question to answer and its arguments. Aliases: @@ -48,21 +49,21 @@ def of(cls: type[SelfMakeNote], question: T_Q) -> SelfMakeNote: return cls(question) @classmethod - def of_the(cls: type[SelfMakeNote], question: T_Q) -> SelfMakeNote: + def of_the(cls, question: T_Q) -> Self: """Alias for :meth:`~screenpy.actions.MakeNote.of`.""" return cls.of(question) - def as_(self: SelfMakeNote, key: str) -> SelfMakeNote: + def as_(self, key: str) -> Self: """Set the key to use to recall this noted value.""" self.key = key return self - def describe(self: SelfMakeNote) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Make a note under {represent_prop(self.key)}." @beat("{} jots something down under {key_to_log}.") - def perform_as(self: SelfMakeNote, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to take a note.""" if self.key is None: msg = "No key was provided to name this note." @@ -81,7 +82,7 @@ def perform_as(self: SelfMakeNote, the_actor: Actor) -> None: Director().notes(self.key, value) def __init__( - self: SelfMakeNote, + self, question: T_Q, key: str | None = None, ) -> None: diff --git a/screenpy/actions/pause.py b/screenpy/actions/pause.py index a1b5c40..421b37e 100644 --- a/screenpy/actions/pause.py +++ b/screenpy/actions/pause.py @@ -10,11 +10,10 @@ from screenpy.pacing import beat if TYPE_CHECKING: - from typing import TypeVar - from screenpy.actor import Actor + from typing_extensions import Self - SelfPause = TypeVar("SelfPause", bound="Pause") + from screenpy.actor import Actor class Pause: @@ -43,11 +42,11 @@ class Pause: reason: str @classmethod - def for_(cls: type[SelfPause], number: float) -> SelfPause: + def for_(cls, number: float) -> Self: """Specify how many seconds or milliseconds to wait for.""" return cls(number) - def seconds_because(self: SelfPause, reason: str) -> SelfPause: + def seconds_because(self, reason: str) -> Self: """Use seconds and provide a reason for the pause. Aliases: @@ -57,23 +56,23 @@ def seconds_because(self: SelfPause, reason: str) -> SelfPause: self.reason = self._massage_reason(reason) return self - def second_because(self: SelfPause, reason: str) -> SelfPause: + def second_because(self, reason: str) -> Self: """Alias for :meth:`~screenpy.actions.Pause.seconds_because`.""" return self.seconds_because(reason) - def milliseconds_because(self: SelfPause, reason: str) -> SelfPause: + def milliseconds_because(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: SelfPause) -> str: + def describe(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: SelfPause, _: Actor) -> None: + def perform_as(self, _: Actor) -> None: """Direct the Actor to take their union-mandated break.""" if not self.reason: msg = ( @@ -84,14 +83,14 @@ def perform_as(self: SelfPause, _: Actor) -> None: sleep(self.time) - def _massage_reason(self: SelfPause, reason: str) -> str: + def _massage_reason(self, reason: str) -> str: """Apply some gentle massaging to the reason string.""" if not reason.startswith("because"): reason = f"because {reason}" return re.sub(r"\W*$", "", reason) - def __init__(self: SelfPause, number: float) -> None: + def __init__(self, number: float) -> None: self.number = number self.time = number self.unit = f"second{'s' if self.number != 1 else ''}" diff --git a/screenpy/actions/see.py b/screenpy/actions/see.py index 0853129..6567950 100644 --- a/screenpy/actions/see.py +++ b/screenpy/actions/see.py @@ -7,15 +7,17 @@ from hamcrest import assert_that from screenpy.pacing import aside, beat -from screenpy.protocols import Answerable, ErrorKeeper, Resolvable +from screenpy.protocols import Answerable, ErrorKeeper from screenpy.speech_tools import get_additive_description, represent_prop if TYPE_CHECKING: - from typing import TypeVar, Union + from typing import Union + + from typing_extensions import Self from screenpy.actor import Actor + from screenpy.protocols import Resolvable - SelfSee = TypeVar("SelfSee", bound="See") T_Q = Union[Answerable, object] T_R = Resolvable @@ -44,16 +46,16 @@ class See: resolution_to_log: str @classmethod - def the(cls: type[SelfSee], question: T_Q, resolution: T_R) -> SelfSee: + def the(cls, question: T_Q, resolution: T_R) -> Self: """Supply the Question (or value) and Resolution to test.""" return cls(question, resolution) - def describe(self: SelfSee) -> str: + def describe(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: SelfSee, the_actor: Actor) -> None: + def perform_as(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) @@ -68,7 +70,7 @@ def perform_as(self: SelfSee, the_actor: Actor) -> None: assert_that(value, self.resolution.resolve(), reason) - def __init__(self: SelfSee, question: T_Q, resolution: T_R) -> None: + def __init__(self, question: T_Q, resolution: T_R) -> None: self.question = question self.question_to_log = get_additive_description(question) self.resolution = resolution diff --git a/screenpy/actions/see_all_of.py b/screenpy/actions/see_all_of.py index 105ed31..e64c020 100644 --- a/screenpy/actions/see_all_of.py +++ b/screenpy/actions/see_all_of.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Tuple, TypeVar +from typing import TYPE_CHECKING, Tuple from screenpy.exceptions import UnableToAct from screenpy.pacing import beat @@ -10,11 +10,12 @@ from .see import See if TYPE_CHECKING: + from typing_extensions import Self + from screenpy.actor import Actor from .see import T_Q, T_R - SelfSeeAllOf = TypeVar("SelfSeeAllOf", bound="SeeAllOf") T_T = Tuple[T_Q, T_R] @@ -43,21 +44,21 @@ class SeeAllOf: tests: tuple[T_T, ...] @classmethod - def the(cls: type[SelfSeeAllOf], *tests: T_T) -> SelfSeeAllOf: + def the(cls, *tests: T_T) -> Self: """Supply any number of Question/value + Resolution tuples to test.""" return cls(*tests) - def describe(self: SelfSeeAllOf) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"See if {self.log_message}." @beat("{} sees if {log_message}:") - def perform_as(self: SelfSeeAllOf, the_actor: Actor) -> None: + def perform_as(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: SelfSeeAllOf, *tests: T_T) -> None: + def __init__(self, *tests: T_T) -> None: for tup in tests: if isinstance(tup, tuple): if len(tup) != 2: # noqa: PLR2004 diff --git a/screenpy/actions/see_any_of.py b/screenpy/actions/see_any_of.py index 137de57..c382621 100644 --- a/screenpy/actions/see_any_of.py +++ b/screenpy/actions/see_any_of.py @@ -10,13 +10,14 @@ from .see import See if TYPE_CHECKING: - from typing import Tuple, TypeVar + from typing import Tuple + + from typing_extensions import Self from screenpy.actor import Actor from .see import T_Q, T_R - SelfSeeAnyOf = TypeVar("SelfSeeAnyOf", bound="SeeAnyOf") T_T = Tuple[T_Q, T_R] @@ -46,16 +47,16 @@ class SeeAnyOf: tests: tuple[T_T, ...] @classmethod - def the(cls: type[SelfSeeAnyOf], *tests: T_T) -> SelfSeeAnyOf: + def the(cls, *tests: T_T) -> Self: """Supply any number of Question/value + Resolution tuples to test.""" return cls(*tests) - def describe(self: SelfSeeAnyOf) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"See if {self.log_message}." @beat("{} sees if {log_message}:") - def perform_as(self: SelfSeeAnyOf, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to make a series of observations.""" if not self.tests: # No tests is OK! @@ -72,7 +73,7 @@ def perform_as(self: SelfSeeAnyOf, the_actor: Actor) -> None: msg = f"{the_actor} did not find any expected answers!" raise AssertionError(msg) - def __init__(self: SelfSeeAnyOf, *tests: T_T) -> None: + def __init__(self, *tests: T_T) -> None: for tup in tests: if isinstance(tup, tuple): if len(tup) != 2: # noqa: PLR2004 diff --git a/screenpy/actor.py b/screenpy/actor.py index c356286..83133cb 100644 --- a/screenpy/actor.py +++ b/screenpy/actor.py @@ -8,13 +8,18 @@ from __future__ import annotations from random import choice -from typing import TypeVar +from typing import TYPE_CHECKING, TypeVar from .exceptions import UnableToPerform from .pacing import aside -from .protocols import Forgettable, Performable +from .protocols import Forgettable from .speech_tools import get_additive_description +if TYPE_CHECKING: + from typing_extensions import Self + + from .protocols import Performable + # pylint: disable=too-many-public-methods ENTRANCE_DIRECTIONS = [ @@ -36,7 +41,6 @@ ] T_Ability = TypeVar("T_Ability", bound=Forgettable) -SelfActor = TypeVar("SelfActor", bound="Actor") class Actor: @@ -55,12 +59,12 @@ class Actor: independent_cleanup_tasks: list[Performable] @classmethod - def named(cls: type[SelfActor], name: str) -> SelfActor: + def named(cls, name: str) -> Self: """Give a name to this Actor.""" aside(choice(ENTRANCE_DIRECTIONS).format(actor=name)) return cls(name) - def who_can(self: SelfActor, *abilities: T_Ability) -> SelfActor: + def who_can(self, *abilities: T_Ability) -> Self: """Add one or more Abilities to this Actor. Aliases: @@ -69,11 +73,11 @@ def who_can(self: SelfActor, *abilities: T_Ability) -> SelfActor: self.abilities.extend(abilities) return self - def can(self: SelfActor, *abilities: T_Ability) -> SelfActor: + def can(self, *abilities: T_Ability) -> Self: """Alias for :meth:`~screenpy.actor.Actor.who_can`.""" return self.who_can(*abilities) - def has_ordered_cleanup_tasks(self: SelfActor, *tasks: Performable) -> SelfActor: + def has_ordered_cleanup_tasks(self, *tasks: Performable) -> Self: """Assign one or more tasks for the Actor to perform when exiting. The tasks given to this method must be performed successfully in @@ -86,13 +90,11 @@ def has_ordered_cleanup_tasks(self: SelfActor, *tasks: Performable) -> SelfActor self.ordered_cleanup_tasks.extend(tasks) return self - def with_ordered_cleanup_tasks(self: SelfActor, *tasks: Performable) -> SelfActor: + def with_ordered_cleanup_tasks(self, *tasks: Performable) -> Self: """Alias for :meth:`~screenpy.actor.Actor.has_ordered_cleanup_tasks`.""" return self.has_ordered_cleanup_tasks(*tasks) - def has_independent_cleanup_tasks( - self: SelfActor, *tasks: Performable - ) -> SelfActor: + def has_independent_cleanup_tasks(self, *tasks: Performable) -> Self: """Assign one or more tasks for the Actor to perform when exiting. The tasks included through this method are assumed to be independent; @@ -105,13 +107,11 @@ def has_independent_cleanup_tasks( self.independent_cleanup_tasks.extend(tasks) return self - def with_independent_cleanup_tasks( - self: SelfActor, *tasks: Performable - ) -> SelfActor: + def with_independent_cleanup_tasks(self, *tasks: Performable) -> Self: """Alias for :meth:`~screenpy.actor.Actor.has_independent_cleanup_tasks`.""" return self.has_independent_cleanup_tasks(*tasks) - def uses_ability_to(self: SelfActor, ability: type[T_Ability]) -> T_Ability: + def uses_ability_to(self, ability: type[T_Ability]) -> T_Ability: """Find the Ability referenced and return it, if the Actor is capable. Raises: @@ -127,11 +127,11 @@ def uses_ability_to(self: SelfActor, ability: type[T_Ability]) -> T_Ability: msg = f"{self} does not have the Ability to {ability}" raise UnableToPerform(msg) - def ability_to(self: SelfActor, ability: type[T_Ability]) -> T_Ability: + def ability_to(self, ability: type[T_Ability]) -> T_Ability: """Alias for :meth:`~screenpy.actor.Actor.uses_ability_to`.""" return self.uses_ability_to(ability) - def has_ability_to(self: SelfActor, ability: type[T_Ability]) -> bool: + def has_ability_to(self, ability: type[T_Ability]) -> bool: """Ask whether the Actor has the Ability to do something.""" try: self.ability_to(ability) @@ -140,7 +140,7 @@ def has_ability_to(self: SelfActor, ability: type[T_Ability]) -> bool: else: return True - def attempts_to(self: SelfActor, *actions: Performable) -> None: + def attempts_to(self, *actions: Performable) -> None: """Perform a list of Actions, one after the other. Aliases: @@ -158,51 +158,51 @@ def attempts_to(self: SelfActor, *actions: Performable) -> None: for action in actions: self.perform(action) - def was_able_to(self: SelfActor, *actions: Performable) -> None: + def was_able_to(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def tries_to(self: SelfActor, *actions: Performable) -> None: + def tries_to(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def tried_to(self: SelfActor, *actions: Performable) -> None: + def tried_to(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def tries(self: SelfActor, *actions: Performable) -> None: + def tries(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def tried(self: SelfActor, *actions: Performable) -> None: + def tried(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def does(self: SelfActor, *actions: Performable) -> None: + def does(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def did(self: SelfActor, *actions: Performable) -> None: + def did(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def will(self: SelfActor, *actions: Performable) -> None: + def will(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def shall(self: SelfActor, *actions: Performable) -> None: + def shall(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def should(self: SelfActor, *actions: Performable) -> None: + def should(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def perform(self: SelfActor, action: Performable) -> None: + def perform(self, action: Performable) -> None: """Perform an Action.""" action.perform_as(self) - def cleans_up_ordered_tasks(self: SelfActor) -> None: + def cleans_up_ordered_tasks(self) -> None: """Perform ordered clean-up tasks.""" try: for task in self.ordered_cleanup_tasks: @@ -210,7 +210,7 @@ def cleans_up_ordered_tasks(self: SelfActor) -> None: finally: self.ordered_cleanup_tasks = [] - def cleans_up_independent_tasks(self: SelfActor) -> None: + def cleans_up_independent_tasks(self) -> None: """Perform independent clean-up tasks.""" for task in self.independent_cleanup_tasks: try: @@ -225,12 +225,12 @@ def cleans_up_independent_tasks(self: SelfActor) -> None: self.independent_cleanup_tasks = [] - def cleans_up(self: SelfActor) -> None: + def cleans_up(self) -> None: """Perform any scheduled clean-up tasks.""" self.cleans_up_independent_tasks() self.cleans_up_ordered_tasks() - def exit(self: SelfActor) -> None: + def exit(self) -> None: """Direct the Actor to forget all their Abilities. Aliases: @@ -243,15 +243,15 @@ def exit(self: SelfActor) -> None: ability.forget() self.abilities = [] - def exit_stage_left(self: SelfActor) -> None: + def exit_stage_left(self) -> None: """Alias for :meth:`~screenpy.actor.Actor.exit`.""" return self.exit() - def exit_stage_right(self: SelfActor) -> None: + def exit_stage_right(self) -> None: """Alias for :meth:`~screenpy.actor.Actor.exit`.""" return self.exit() - def exit_through_vomitorium(self: SelfActor) -> None: + def exit_through_vomitorium(self) -> None: """Alias for :meth:`~screenpy.actor.Actor.exit`.""" return self.exit() @@ -259,11 +259,11 @@ def __call__(self, *actions: Performable) -> None: """Alias for :meth:`~screenpy.actor.Actor.attempts_to`.""" return self.attempts_to(*actions) - def __repr__(self: SelfActor) -> str: + def __repr__(self) -> str: """The name of the Actor represents them.""" return self.name - def __init__(self: SelfActor, name: str) -> None: + def __init__(self, name: str) -> None: self.name = name self.abilities = [] self.ordered_cleanup_tasks = [] diff --git a/screenpy/director.py b/screenpy/director.py index 31557ab..d1265b0 100644 --- a/screenpy/director.py +++ b/screenpy/director.py @@ -6,9 +6,10 @@ from __future__ import annotations -from typing import Any, TypeVar +from typing import TYPE_CHECKING, Any -SelfDirector = TypeVar("SelfDirector", bound="Director") +if TYPE_CHECKING: + from typing_extensions import Self class Director: @@ -22,7 +23,7 @@ class Director: _instance = None - def __new__(cls: type[SelfDirector]) -> SelfDirector: + def __new__(cls) -> Self: """Ensure there is only one Director.""" if cls._instance is None: cls._instance = super().__new__(cls) @@ -30,11 +31,11 @@ def __new__(cls: type[SelfDirector]) -> SelfDirector: return cls._instance # ANN401 ignored here because the Director can note anything! - def notes(self: SelfDirector, key: str, value: Any) -> None: # noqa: ANN401 + def notes(self, key: str, value: Any) -> None: # noqa: ANN401 """Note down a value under the given key.""" self.notebook[key] = value # ANN401 ignored here because the Director can note anything! - def looks_up(self: SelfDirector, key: str) -> Any: # noqa: ANN401 + def looks_up(self, key: str) -> Any: # noqa: ANN401 """Look up a noted value by its key.""" return self.notebook[key] diff --git a/tests/test_pacing_future.py b/tests/test_pacing_future.py index 0c55eb9..44ac4f3 100644 --- a/tests/test_pacing_future.py +++ b/tests/test_pacing_future.py @@ -5,11 +5,13 @@ import logging from typing import TYPE_CHECKING -from screenpy import Actor, IsEqualTo, See, beat +from screenpy import IsEqualTo, See, beat if TYPE_CHECKING: import pytest + from screenpy import Actor + class CornerCase: @beat("{} examines CornerCase")