Skip to content
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

Ruff enable fbt #47

Merged
merged 10 commits into from
Feb 14, 2024
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.

bandophahita marked this conversation as resolved.
Show resolved Hide resolved
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
Loading