Skip to content

Commit

Permalink
Ruff enable fbt (ScreenPyHQ#47)
Browse files Browse the repository at this point in the history
* enabling ruff FBT (flake8-boolean-trap) and addressing ScreenPyHQ#44
* fixing 3.8/3.9 compatability with ParamSpec
* fixing docstring capitalization
* updated deprecation warning to inform users when the change will take place.
* tests should assert reason for failure
  • Loading branch information
bandophahita authored Feb 14, 2024
1 parent 89b64d7 commit dd85ed2
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 7 deletions.
107 changes: 107 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
============
Deprecations
============

This page documents
the major deprecations
in ScreenPy Selenium's life,
and how to adjust your tests
to keep them up to date.

4.1.0 Deprecations
==================

Boolean Positional Arguments Deprecation
----------------------------------------

The following class constructors
have been marked deprecated
when using a positional boolean argument in the constructor.
Starting with version 5.0.0
you will be required to provide keywords
for the following boolean arguments.

While our documentation does not explicitly outline using these Actions in this way,
it's still possible to do so.
If you are using Actions directly from their constructors
like the below examples,
here are the fixes you'll need to implement.


:class:`~screenpy_selenium.actions.enter.Enter`

Before:

.. code-block:: python
the_actor.will(Enter("foo", True).into_the(PASSWORD))
After:

.. code-block:: python
the_actor.will(Enter("foo", mask=True).into_the(PASSWORD))
:class:`~screenpy_selenium.actions.hold_down.HoldDown`

Before:

.. code-block:: python
the_actor.will(Chain(HoldDown(None, True))
After:
.. code-block:: python
the_actor.will(Chain(HoldDown(None, lmb=True))
the_actor.will(Chain(HoldDown(lmb=True))
:class:`~screenpy_selenium.actions.release.Release`
Before:
.. code-block:: python
the_actor.will(Release(None, True))
After:
.. code-block:: python
the_actor.will(Release(None, lmb=True))
the_actor.will(Release(lmb=True))
:class:`~screenpy_selenium.questions.selected.Selected`
Before:
.. code-block:: python
the_actor.shall(See.the(Selected(TARGET, True), IsEmpty()))
After:
.. code-block:: python
the_actor.shall(See.the(Selected(TARGET, multi=True), IsEmpty()))
:class:`~screenpy_selenium.questions.text.Text`
Before:
.. code-block:: python
the_actor.shall(See.the(Text(TARGET, True), IsEqual("foo"))
After:
.. code-block:: python
the_actor.shall(See.the(Text(TARGET, multi=True), IsEqual("foo")
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ to :class:`~screenpy_selenium.abilities.BrowseTheWeb`!
extended_api
targets
cookbook
deprecations
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ select = [
"ERA", # eradicate
"F", # Pyflakes
"FA", # flake8-future-annotations
# "FBT", # flake8-boolean-trap
"FBT", # flake8-boolean-trap
"FIX", # flake8-fixme
"FLY", # flynt
"I", # isort
Expand Down
6 changes: 5 additions & 1 deletion screenpy_selenium/actions/enter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from screenpy.pacing import aside, beat
from selenium.common.exceptions import WebDriverException

from ..common import pos_args_deprecated
from ..speech_tools import KEY_NAMES

if TYPE_CHECKING:
Expand Down Expand Up @@ -152,7 +153,10 @@ def add_to_chain(
for key in self.following_keys:
send_keys(key)

def __init__(self: SelfEnter, text: str, mask: bool = False) -> None:
@pos_args_deprecated("mask")
def __init__(
self: SelfEnter, text: str, mask: bool = False # noqa: FBT001, FBT002
) -> None:
self.text = text
self.target = None
self.following_keys = []
Expand Down
8 changes: 7 additions & 1 deletion screenpy_selenium/actions/hold_down.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from screenpy.pacing import beat
from selenium.webdriver.common.keys import Keys

from ..common import pos_args_deprecated
from ..speech_tools import KEY_NAMES

if TYPE_CHECKING:
Expand Down Expand Up @@ -88,7 +89,12 @@ def add_to_chain(
msg = "HoldDown must be told what to hold down."
raise UnableToAct(msg)

def __init__(self: SelfHoldDown, key: str | None = None, lmb: bool = False) -> None:
@pos_args_deprecated("lmb")
def __init__(
self: SelfHoldDown,
key: str | None = None,
lmb: bool = False, # noqa: FBT001, FBT002
) -> None:
self.key = key
self.lmb = lmb
self.target = None
Expand Down
8 changes: 7 additions & 1 deletion screenpy_selenium/actions/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from screenpy.pacing import beat
from selenium.webdriver.common.keys import Keys

from ..common import pos_args_deprecated
from ..speech_tools import KEY_NAMES

if TYPE_CHECKING:
Expand Down Expand Up @@ -75,7 +76,12 @@ def add_to_chain(self: SelfRelease, _: Actor, the_chain: ActionChains) -> None:
msg = "Release must be told what to release."
raise UnableToAct(msg)

def __init__(self: SelfRelease, key: str | None = None, lmb: bool = False) -> None:
@pos_args_deprecated("lmb")
def __init__(
self: SelfRelease,
key: str | None = None,
lmb: bool = False, # noqa: FBT001, FBT002
) -> None:
self.key = key
self.lmb = lmb
self.description = "LEFT MOUSE BUTTON" if lmb else KEY_NAMES[key]
Expand Down
46 changes: 46 additions & 0 deletions screenpy_selenium/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Module to hold shared objects."""

from __future__ import annotations

import warnings
from functools import wraps
from typing import TYPE_CHECKING, Callable, TypeVar

from typing_extensions import ParamSpec

if TYPE_CHECKING:
P = ParamSpec("P")
T = TypeVar("T")
Function = Callable[P, T]


def pos_args_deprecated(*keywords: str) -> Function:
"""Warn users which positional arguments should be called via keyword."""

def deprecated(func: Function) -> Function:
argnames = func.__code__.co_varnames[: func.__code__.co_argcount]
i = min([argnames.index(kw) for kw in keywords])
kw_argnames = argnames[i:]

@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Function:
# call the function first, to make sure the signature matches
ret_value = func(*args, **kwargs)

args_that_should_be_kw = args[i:]
if args_that_should_be_kw:
posargnames = ", ".join(kw_argnames)

msg = (
f"Warning: positional arguments `{posargnames}` for "
f"`{func.__qualname__}` are deprecated "
f"and will be removed in version 5. "
f"Please use keyword arguments instead."
)
warnings.warn(msg, DeprecationWarning, stacklevel=2)

return ret_value

return wrapper

return deprecated
7 changes: 6 additions & 1 deletion screenpy_selenium/questions/selected.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from screenpy.pacing import beat
from selenium.webdriver.support.ui import Select as SeleniumSelect

from ..common import pos_args_deprecated

if TYPE_CHECKING:
from screenpy import Actor

Expand Down Expand Up @@ -90,6 +92,9 @@ def answered_by(self: SelfSelected, the_actor: Actor) -> str | list[str]:
return [e.text for e in select.all_selected_options]
return select.first_selected_option.text

def __init__(self: SelfSelected, target: Target, multi: bool = False) -> None:
@pos_args_deprecated("multi")
def __init__(
self: SelfSelected, target: Target, multi: bool = False # noqa: FBT001, FBT002
) -> None:
self.target = target
self.multi = multi
7 changes: 6 additions & 1 deletion screenpy_selenium/questions/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from screenpy.pacing import beat

from ..common import pos_args_deprecated

if TYPE_CHECKING:
from screenpy import Actor

Expand Down Expand Up @@ -70,6 +72,9 @@ def answered_by(self: SelfText, the_actor: Actor) -> str | list[str]:
return [e.text for e in self.target.all_found_by(the_actor)]
return self.target.found_by(the_actor).text

def __init__(self: SelfText, target: Target, multi: bool = False) -> None:
@pos_args_deprecated("multi")
def __init__(
self: SelfText, target: Target, multi: bool = False # noqa: FBT001, FBT002
) -> None:
self.target = target
self.multi = multi
57 changes: 56 additions & 1 deletion tests/test_actions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

from typing import cast
import warnings
from contextlib import contextmanager
from typing import Generator, cast
from unittest import mock

import pytest
Expand Down Expand Up @@ -63,6 +65,20 @@
TARGET = FakeTarget()


@contextmanager
def not_raises(ExpectedException: type[Exception]) -> Generator:
try:
yield

except ExpectedException as error:
msg = f"Incorrectly Raised {error}"
raise AssertionError(msg) from error

except Exception as error: # noqa: BLE001
msg = f"Unexpected exception {error}"
raise AssertionError(msg) from error


class TestAcceptAlert:
def test_can_be_instantiated(self) -> None:
aa = AcceptAlert()
Expand Down Expand Up @@ -435,6 +451,19 @@ def new_method(self) -> bool:

assert SubEnter.the_text("blah").new_method() is True

def test_positional_arg_warns(self) -> None:
with pytest.warns(DeprecationWarning):
Enter("", True)

def test_keyword_arg_does_not_warn(self) -> None:
with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
Enter.the_secret("")

with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
Enter("", mask=True)


class TestEnter2FAToken:
def test_can_be_instantiated(self) -> None:
Expand Down Expand Up @@ -619,6 +648,19 @@ def new_method(self) -> bool:

assert SubHoldDown.left_mouse_button().new_method() is True

def test_positional_arg_warns(self) -> None:
with pytest.warns(DeprecationWarning):
HoldDown(Keys.LEFT_ALT, True)

def test_keyword_arg_does_not_warn(self) -> None:
with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
HoldDown.left_mouse_button()

with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
HoldDown(lmb=True)


class TestMoveMouse:
def test_can_be_instantiated(self) -> None:
Expand Down Expand Up @@ -884,6 +926,19 @@ def new_method(self) -> bool:

assert SubRelease.left_mouse_button().new_method() is True

def test_positional_arg_warns(self) -> None:
with pytest.warns(DeprecationWarning):
Release(Keys.LEFT_ALT, True)

def test_keyword_arg_does_not_warn(self) -> None:
with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
Release.left_mouse_button()

with not_raises(DeprecationWarning), warnings.catch_warnings():
warnings.simplefilter("error")
Release(lmb=True)


class TestRespondToThePrompt:
def test_can_be_instantiated(self) -> None:
Expand Down
Loading

0 comments on commit dd85ed2

Please sign in to comment.