Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .basedpyright/baseline.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
{
"files": {
"./pytest_robotframework/_internal/pytest/robot_file_support.py": [
{
"code": "reportUnusedCallResult",
"range": {
"startColumn": 16,
"endColumn": 66,
"lineCount": 1
}
}
],
"./tests/conftest.py": [
{
"code": "reportUnusedCallResult",
Expand All @@ -27,16 +17,6 @@
"lineCount": 6
}
}
],
"./tests/fixtures/test_python/test_robot_keyword_in_python_test/test_foo.py": [
{
"code": "reportUnusedCallResult",
"range": {
"startColumn": 4,
"endColumn": 32,
"lineCount": 1
}
}
]
}
}
2 changes: 1 addition & 1 deletion pw.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[main]
requirements = ["uv==0.9.16"]
requirements = ["uv==0.9.18"]
hash = "45210da832f9626829457a65e9e7c4d0"
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ reportIncompatibleUnannotatedOverride = 'error'
division-by-zero = "error"
possibly-unresolved-reference = "warn"
unused-ignore-comment = "warn"
possibly-missing-import = "error"

[tool.ty.environment]
python-platform = "all"
Expand Down
26 changes: 14 additions & 12 deletions pytest_robotframework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def inner(
/,
*args: P.args,
**kwargs: P.kwargs,
) -> T: # ty:ignore[invalid-method-override] https://github.com/astral-sh/ty/issues/1820
) -> T:
T_WrappedContextManager = TypeVar("T_WrappedContextManager")

@final
Expand Down Expand Up @@ -650,17 +650,19 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
list[tuple[str, Function]],
inspect.getmembers(
cls,
predicate=lambda attr: inspect.isfunction(attr) # pyright:ignore[reportAny]
# the wrapper breaks static methods idk why, but we shouldn't need to wrap them anyway
# because robot listeners/suite visitors don't call any static/class methods
and not isinstance(
inspect.getattr_static(cls, attr.__name__), (staticmethod, classmethod)
)
# only wrap methods that are overwritten on the subclass
and attr.__name__ in vars(cls)
# don't wrap private/dunder methods since they'll get called by the public ones and we
# don't want to duplicate errors
and not attr.__name__.startswith("_"),
predicate=lambda attr: ( # pyright:ignore[reportAny]
inspect.isfunction(attr) # pyright:ignore[reportAny]
# the wrapper breaks static methods idk why, but we shouldn't need to wrap them
# anyway because robot listeners/suite visitors don't call any static/class methods
and not isinstance(
inspect.getattr_static(cls, attr.__name__), (staticmethod, classmethod)
)
# only wrap methods that are overwritten on the subclass
and attr.__name__ in vars(cls)
# don't wrap private/dunder methods since they'll get called by the public ones and
# we don't want to duplicate errors
and not attr.__name__.startswith("_")
),
),
):
setattr(cls, name, wrapped(method))
Expand Down
4 changes: 2 additions & 2 deletions pytest_robotframework/_internal/pytest/robot_file_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from _pytest._code.code import ReprFileLocation, TerminalRepr
from pytest import Config, ExceptionInfo, File, Item, MarkDecorator, Session, StashKey, mark, skip
from robot.errors import ExecutionFailures, ExecutionStatus, RobotError
from robot.libraries.BuiltIn import BuiltIn
from robot.running.bodyrunner import BodyRunner
from robot.running.statusreporter import StatusReporter
from typing_extensions import override
Expand All @@ -20,6 +19,7 @@
execution_context,
get_arg_with_type,
robot_6,
run_keyword,
running_test_case_key,
)
from pytest_robotframework._internal.utils import P, patch_method
Expand Down Expand Up @@ -127,7 +127,7 @@ def _check_execution_status() -> Iterator[None]:
def _run_keyword(self, keyword: model.Keyword | None):
if keyword and keyword.name is not None and keyword.name.lower() != "none":
with self._check_execution_status():
BuiltIn().run_keyword(keyword.name, *keyword.args)
run_keyword(keyword.name, *keyword.args)

@override
def setup(self):
Expand Down
19 changes: 18 additions & 1 deletion pytest_robotframework/_internal/robot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from robot import model, running
from robot.api.interfaces import ListenerV2, ListenerV3, Parser
from robot.conf.settings import RobotSettings, _BaseSettings # pyright:ignore[reportPrivateUsage]
from robot.libraries.BuiltIn import BuiltIn
from robot.running.context import _ExecutionContext # pyright:ignore[reportPrivateUsage]
from robot.version import VERSION
from typing_extensions import override
Expand Down Expand Up @@ -245,7 +246,7 @@ def merge_robot_options(*robot_options: InternalRobotOptions) -> dict[str, objec
merges two dicts of robot options, combining lists, or overriding them with `None` if a later
object explicitly sets the value to `None`
"""
return reduce(_merge_robot_options, robot_options, {})
return dict(reduce(_merge_robot_options, robot_options, {}))


def cli_defaults(settings_class: Callable[[dict[str, object]], _BaseSettings]) -> RobotOptions:
Expand Down Expand Up @@ -289,3 +290,19 @@ def is_robot_traceback(tb: TracebackType) -> bool | str | None:
main_package_name,
))
)


def run_keyword(name: str, *args: str):
"""
robot 7.4 introduced these stupid nonsense fake types for keyword names and arguments, so
this utility function just wraps the original `run_keyword` method but with the correct types.
"""
builtin = BuiltIn()
# this modiule was only introduced in robot 7.4, so fall back to calling the method normally if
# it doesn't exist
try:
from robot.api.types import KeywordArgument, KeywordName # noqa: PLC0415
except ModuleNotFoundError:
builtin.run_keyword(name, *args) # pyright: ignore[reportUnusedCallResult, reportArgumentType]
else:
_ = builtin.run_keyword(KeywordName(name), *(cast(tuple[KeywordArgument], args)))
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from __future__ import annotations

from robot.libraries.BuiltIn import BuiltIn

from pytest_robotframework import import_resource
from pytest_robotframework._internal.robot.utils import run_keyword

import_resource("bar/bar.resource")


def test_foo():
BuiltIn().run_keyword("bar")
run_keyword("bar")
2 changes: 1 addition & 1 deletion tests/type_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@keyword(name="foo bar", tags=("a", "b"))
def a() -> None: ...

# https://github.com/astral-sh/ty/issues/1821
# https://github.com/astral-sh/ty/issues/1495#issuecomment-3668125131

_ = assert_type(a, Callable[[], None]) # ty:ignore[type-assertion-failure]

Expand Down
Loading