Skip to content

some screenpy module Questions aren't recognized by mypy as type 'Answerable' #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
langgaibo opened this issue May 10, 2022 · 3 comments

Comments

@langgaibo
Copy link
Member

langgaibo commented May 10, 2022

After refactoring our codebase to use Screenpy 4.0 and various screenpy modules, we have a few mypy complaints related to typing issues in interactions between modules, specifically with base screenpy's MakeNote action.

First and most straightforward, we have the screenpy_requests Cookies question. Here's a simplified example Task from our code:

# tasks/update_csrf_token.py
from screenpy.actions import MakeNote
from screenpy_requests.questions import Cookies
# ...
the_actor.attempts_to(
    SendGETRequest.to(CSRF_TOKEN_URL),
    MakeNote.of_the(Cookies()).as_("cookies"),
)

And mypy's complaint:

update_csrf_token.py:46: error: Too few arguments
update_csrf_token.py:46: error: Argument 1 has incompatible type "Cookies"; expected "Type[MakeNote]"

Secondly, and this may just be something we need to address in our own code with better type declaration, we have an interaction with a custom question and MakeNote.

Here's a simplified version of a custom question of ours:

# questions/appointment_time.py
import re
from screenpy import Actor
from screenpy.pacing import beat
from screenpy_selenium.questions import Text

class AppointmentTime:
    @beat("{} examines the time of the selected appointment.")
    def answered_by(self, the_actor: Actor) -> str:
        """Direct the Actor to find the appointment time."""
        reservation_string = Text.of_the(ELEMENT).answered_by(the_actor)
        match = re.search(r"{some regex}", reservation_string)
        return f"{match.group('time')} {match.group('meridian').upper()}"

And a simplified version of the task that calls it:

@beat("{} confirms the selected appointment time.")
def perform_as(self, the_actor: Actor) -> None:
    """Direct the actor to confirm the selected appointment time."""
    the_actor.attempts_to(
        Eventually(
            MakeNote.of_the(AppointmentTime()).as_("selected appointment time")
        ),

and mypy's complaint:

tasks/confirm_appt_time.py:33: error: Too few arguments
tasks/confirm_appt_time.py:33: error: Argument 1 has incompatible type "AppointmentTime"; expected "Type[MakeNote]"

We are using MakeNote against several Questions from the various screenpy modules as well as several of our own custom questions, but for the purposes of this issue, these are the only two Questions that are tripping mypy.
I looked at other Questions being used by MakeNote and can't see a meaningful difference in how they're declaring their types: we have many that return str as does AppointmentTime above.

@perrygoy
Copy link
Member

perrygoy commented May 10, 2022

Thanks @langgaibo for reporting this!

The problem lies with aliasing the classmethods, as you can see in MyPy#6700.

The solution would be to redefine all of the aliases as separate classmethods, which might actually help for documentation. Right now, the ReadTheDocs autogenerated documentation shows all the aliases as just copies of the original, which can be kind of annoying. It would be nice to just mention the aliases in the docstring of the original and have Sphinx skip the documentation for the aliases.

@bandophahita
Copy link
Contributor

Would the newly created classmethods look like this?

    @classmethod
    def of_the(cls, question: Union[Answerable, Any]) -> "MakeNote":
        return cls.of(question)

or just legit copies?

    @classmethod
    def of_the(cls, question: Union[Answerable, Any]) -> "MakeNote":
        return cls(question)

@perrygoy
Copy link
Member

The former, i'll have a PR up momentarily!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants