diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9fba4973..2099a19c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { "recommendations": [ "ms-python.python", - "ms-python.vscode-pylance", + "detachhead.basedpyright", "ms-python.pylint", "ms-python.black-formatter", "ms-python.mypy-type-checker", @@ -11,5 +11,8 @@ "tamasfe.even-better-toml", "redhat.vscode-yaml", "robocorp.robotframework-lsp" + ], + "unwantedRecommendations": [ + "ms-python.vscode-pylance", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 693452dc..966bf0e5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,12 +10,6 @@ "git.useEditorAsCommitInput": false, "diffEditor.ignoreTrimWhitespace": false, "terminal.integrated.persistentSessionReviveProcess": "never", - "mypy-type-checker.args": [ - "--ide", - "--python-version", - "3.8" - ], - "mypy-type-checker.importStrategy": "fromEnvironment", "files.autoSave": "onFocusChange", "search.useIgnoreFiles": true, "git.useCommitInputAsStashMessage": true, @@ -28,9 +22,6 @@ "python.analysis.typeCheckingMode": "off", "python.analysis.autoFormatStrings": true, "python.analysis.gotoDefinitionInStringLiteral": true, - "python.analysis.typeshedPaths": [ - "./.venv/lib/site-packages/mypy/typeshed" - ], "pylint.importStrategy": "fromEnvironment", "pylint.severity": { "convention": "Warning", @@ -56,8 +47,5 @@ "search.exclude": { "pw": true }, - "mypy-type-checker.ignorePatterns": [ - "pw" - ], "python.terminal.activateEnvInCurrentTerminal": true } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8174f90a..acfa3d57 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -34,10 +34,10 @@ "problemMatcher": [] }, { - "label": "basedmypy - all files", + "label": "basedpyright - all files", "type": "shell", "command": "./pw", - "args": ["pdm", "run", "mypy_all"], + "args": ["run", "basedpyright"], "presentation": { "clear": true }, diff --git a/pdm.lock b/pdm.lock index 6ca08ffb..bc79eb26 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "lint", "test", "docs"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:f92e05170012c8f9371d06b7c6d97e4a5f5660d11a60afca9366ac3b50669900" +content_hash = "sha256:73ada6b5f26d53ee49eeb32469452c718c7f6b0298c8226fc067cbb7e81c851e" [[package]] name = "astroid" @@ -34,44 +34,15 @@ files = [ ] [[package]] -name = "basedmypy" -version = "2.3.0" +name = "basedpyright" +version = "0.1.0" requires_python = ">=3.8" -summary = "Based static typing for Python" +git = "https://github.com/detachhead/basedpyright.git" +ref = "d0b3dbe33add247adf58b2b24f30953cf15d97e3" +revision = "d0b3dbe33add247adf58b2b24f30953cf15d97e3" +summary = "" dependencies = [ - "basedtyping>=0.0.3", - "mypy-extensions>=1.0.0", - "tomli>=1.1.0; python_version < \"3.11\"", - "typing-extensions>=4.1.0", -] -files = [ - {file = "basedmypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c38ea60a186ec5b4e5800c6b30f0d27d907eaf025a647c5d376c6e12b85dadde"}, - {file = "basedmypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19f21c43cfe4e8617410a7460863477a1be35b6125b80cc767c1aa1df148de0c"}, - {file = "basedmypy-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b6b752f40e1d21e17500cf8041cd756d430b82cbf4ffce1f1b5b46593f8ce1"}, - {file = "basedmypy-2.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:32da5239645abc17c2773becb6e42bfebf63134ac31db528906e7e7e8ed7067c"}, - {file = "basedmypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3636e8d5a61124f2c2a262294ca0d97cc19245a677115ad99b90724087e6443"}, - {file = "basedmypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b40542cce22b9b84d59631f1c09556b82bc6ca96f075a527390055154b1197b3"}, - {file = "basedmypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47e5d1529eccd3d890c66c69f75601d629d2402334d729c94ab4a34dfde250d2"}, - {file = "basedmypy-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01ebdbc8c0fe55d4493388d00802fa46a8f9a131ab98fa8d068de599f09d484"}, - {file = "basedmypy-2.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b882c8ccf24f3fb4df6c61357e58837dbfef177b235f818a5b258f41ff5b67e"}, - {file = "basedmypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc90c2b54c7948866699ffb7800c309f3d94ff6174b507ea63d775873a35b63e"}, - {file = "basedmypy-2.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d2a11517384ef58ac02cea60939ee8db9b948c3a19209d811ccd389ce229da32"}, - {file = "basedmypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e8a3db700f6296ec4fdb3565c8f222d53f7e0f5756eec68ef42303862b8e5e0"}, - {file = "basedmypy-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95507d9f9c93b378f88990efba35a46ad99ff1452f4dae76cec87ab17212ef31"}, - {file = "basedmypy-2.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a19e0cba3573e9f4d3d8f59162e1412d8523fd10e250b53f28907367ec2372c9"}, - {file = "basedmypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:42cbad82650345b41f13d7a5d86141502c8384f13bcab087550d6cfb5f66fdfb"}, - {file = "basedmypy-2.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:63d3621016dbfd568e464139326eb929532d18c597d6a6033c3f3f45c16f527c"}, - {file = "basedmypy-2.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8e69209909e454b42cd9337af89ad401207dc3dd315a180e3cc3e3905372d297"}, - {file = "basedmypy-2.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f3f5bfb767b8110345e3c9b1122fc1841433b623fea7849c7ddfdaf82276fd"}, - {file = "basedmypy-2.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:808309efb6a09004b1fe58eddcb337ba68c42a8cf0accda890d0dea203947f87"}, - {file = "basedmypy-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9847cc608b4581c1ff8d2364ca0afc014bc222f86d6f06645488352469a25b8"}, - {file = "basedmypy-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4109fad62459ecbd88097d8e5340afeda400ace34586c44d87d09b668f9d44d0"}, - {file = "basedmypy-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d8805a62248002ff79ae95c63f06f8df9a18b6d15d2592b843dc351e14be778"}, - {file = "basedmypy-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a724142bcbfe7dcf863457d100eec98ae9412b12f8422121c1708240f57dbd"}, - {file = "basedmypy-2.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fd17acb3eb2da752460442ace69cb71adbb401434724258141770bb97d139fd"}, - {file = "basedmypy-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:1c5d471541a1f33dc6fc5e41876e07c0c42dcc93d400df2ab0abbc6d4e2340e8"}, - {file = "basedmypy-2.3.0-py3-none-any.whl", hash = "sha256:8e36e6ad996203d3f524afb6c96adc4b33203d94f35ff8703790629f117675ee"}, - {file = "basedmypy-2.3.0.tar.gz", hash = "sha256:0c4bafc220c65fb75ef6dcf9e44294ec7dbc317e385e96c57e40a8b4eccde4bc"}, + "nodejs-bin>=18.4.0a4", ] [[package]] @@ -402,6 +373,21 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodejs-bin" +version = "18.4.0a4" +requires_python = "~=3.5" +summary = " Node.js is an open-source, cross-platform, back-end JavaScript\nruntime environment that runs on the V8 engine and executes JavaScript code\noutside a web browser." +files = [ + {file = "nodejs_bin-18.4.0a4-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:16cb1abf7fe8c11c574e1e474d9f934a0df49a480290eae6e733d8bb09512e22"}, + {file = "nodejs_bin-18.4.0a4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:068ca987ed83ea1123775fafe5dc22d8f2ff920d7d31571e1bfe6fb1093833eb"}, + {file = "nodejs_bin-18.4.0a4-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:06cfeaa4d26eec94d8edb9927525ce94eb96dadc81f7d1daed42d1a7d003a4c9"}, + {file = "nodejs_bin-18.4.0a4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:431ee3529f4fb226ddcfd4f14cb37e7df31238c42dfd051f4bf8f0c21029b133"}, + {file = "nodejs_bin-18.4.0a4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21f1f77ddc8fe05353bb6d6ee8e5a62edb3a8dcdb2740a5f9307fd8d9eef6691"}, + {file = "nodejs_bin-18.4.0a4-py3-none-win32.whl", hash = "sha256:59671fdc563dabb8be8a0b6dae4169d780482b3c9e0fba3f9aa2b7ee8d2261ac"}, + {file = "nodejs_bin-18.4.0a4-py3-none-win_amd64.whl", hash = "sha256:cbd509218b4b17f75ee7841f9c21d5cacc1626d3b823a652a6627dbad18228ec"}, +] + [[package]] name = "packaging" version = "23.2" diff --git a/pyproject.toml b/pyproject.toml index b6639ff1..a9cd868d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ test = "pdm run pytest -n auto" [tool.pdm.dev-dependencies] lint = [ "black>=23", - "basedmypy>=2.1", + "basedpyright @ git+https://github.com/detachhead/basedpyright.git@d0b3dbe33add247adf58b2b24f30953cf15d97e3", "pylint>=3.0.0a7", "ruff>=0.0.290", "robotframework-robocop>=4.1.0", @@ -44,12 +44,6 @@ lint = [ test = ["lxml>=4.9.3", "lxml-stubs>=0.4.0", "pytest-xdist>=3.5.0"] docs = ["pdoc>=14.1.0"] -# maybe these should be pyprojectx scripts instead once https://github.com/pyprojectx/pyprojectx/issues/26 is fixed -[tool.pdm.scripts] -_mypy_package = { cmd = "pdm run mypy -p pytest_robotframework" } -_mypy_tests = { cmd = "pdm run mypy -p tests" } -mypy_all = { composite = ["_mypy_package", "_mypy_tests"] } - [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" @@ -103,7 +97,6 @@ enable = [ "possibly-unused-variable", "access-member-before-definition", "assigning-non-slot", - "assignment-from-no-return", "assignment-from-none", "bad-except-order", "bad-exception-cause", @@ -268,36 +261,73 @@ addopts = ['-p no:robotframework', '--ignore=tests/fixtures'] xfail_strict = true enable_assertion_pass_hook = true -[tool.mypy] -allow_redefinition = false -enable_error_code = "helpful-string" -default_return = false # enabled for our own code only as 3rd party code is not coompatible -cache_dir = 'nul' # disable cache because it sucks - -[[tool.mypy.overrides]] -module = ['pytest_robotframework.*', 'tests.*'] -default_return = true - -[[tool.mypy.overrides]] -module = ['robot.*'] -no_implicit_reexport = false -ignore_missing_py_typed = true # https://github.com/robotframework/robotframework/issues/4822 - -[[tool.mypy.overrides]] -module = ['deepmerge.*'] -ignore_missing_py_typed = true - - [tool.pyright] -# we dont use pyright for type checking, this is just for vscode import suggestions and reachability checks pythonVersion = "3.8" pythonPlatform = "All" +typeCheckingMode = "strict" +reportMissingTypeStubs = false +strictListInference = true +strictDictionaryInference = true +strictSetInference = true +deprecateTypingAliases = true +enableExperimentalFeatures = true +disableBytesTypePromotions = true +reportPropertyTypeMismatch = true +reportFunctionMemberAccess = true +reportMissingModuleSource = true +reportImportCycles = true +reportUnusedClass = true +reportUnusedFunction = true +reportUnusedVariable = true +reportDuplicateImport = true +reportWildcardImportFromLibrary = true +reportUntypedFunctionDecorator = true +reportUntypedClassDecorator = true +reportUntypedBaseClass = true +reportUntypedNamedTuple = true +reportPrivateUsage = true +reportTypeCommentUsage = true +reportConstantRedefinition = true +reportDeprecated = true +reportIncompatibleMethodOverride = true +reportIncompatibleVariableOverride = true +reportInconsistentConstructor = true +reportOverlappingOverload = true +reportMissingSuperCall = true +reportUninitializedInstanceVariable = true +reportInvalidStringEscapeSequence = true +reportUnknownParameterType = true +reportUnknownArgumentType = true +reportUnknownLambdaType = true +reportUnknownVariableType = true +reportUnknownMemberType = true +reportMissingParameterType = true +reportMissingTypeArgument = true +reportInvalidTypeVarUse = true +reportCallInDefaultInitializer = true +reportUnnecessaryIsInstance = true +reportUnnecessaryCast = true +reportUnnecessaryComparison = true +reportUnnecessaryContains = true +reportAssertAlwaysTrue = true +reportSelfClsParameterName = true +reportImplicitStringConcatenation = false # conflicts with black +reportInvalidStubStatement = true +reportIncompleteStub = true +reportUnsupportedDunderAll = true +reportUnusedCallResult = true +reportUnusedCoroutine = true +reportUnusedExpression = true +reportUnnecessaryTypeIgnoreComment = true +reportMatchNotExhaustive = true +reportImplicitOverride = true +reportShadowedImports = true [tool.ruff] unsafe-fixes = true extend-select = ["ALL"] ignore = [ - "ANN", # flake8-annotations (covered by mypy) + "ANN", # flake8-annotations (covered by pyright) "COM", # flake8-commas (covered by black) "EM", # flake8-errmsg "FIX", # flake8-fixme @@ -307,13 +337,13 @@ ignore = [ "PLR2004", # Magic value used in comparison "PLR1722", # Use `sys.exit()` instead of `exit` "PLW2901", # `for` loop variable overwritten by assignment target - "PLE0605", # Invalid format for `__all__`, must be `tuple` or `list` (covered by mypy) + "PLE0605", # Invalid format for `__all__`, must be `tuple` or `list` (covered by pyright) "PLR0911", # Too many return statements "PLW0603", # Using the global statement is discouraged "PLC0105", # `TypeVar` name does not reflect its covariance - "PLC0414", # Import alias does not rename original package (used by mypy for explicit re-export) - "RUF013", # PEP 484 prohibits implicit Optional (covered by mypy) - "RUF016", # Slice in indexed access to type (covered by mypy) + "PLC0414", # Import alias does not rename original package (used by pyright for explicit re-export) + "RUF013", # PEP 484 prohibits implicit Optional (covered by pyright) + "RUF016", # Slice in indexed access to type (covered by pyright) "TRY002", # Create your own exception "TRY003", # Avoid specifying long messages outside the exception class "D10", # Missing docstring @@ -334,10 +364,10 @@ ignore = [ "D418", # Function/Method decorated with @overload shouldn't contain a docstring (vscode supports it) "PT013", # Found incorrect import of pytest, use simple import pytest instead (only for bad linters that can't check the qualname) "TD002", # Missing author in TODO - "PGH003", # Use specific rule codes when ignoring type issues (covered by mypy) "E701", # Multiple statements on one line (sometimes conflicts with black) "CPY001", # missing-copyright-notice "C901", # max-complexity + "SLF001", # private-member-access (covered by pyright) ] target-version = "py38" respect-gitignore = true diff --git a/pytest_robotframework/__init__.py b/pytest_robotframework/__init__.py index ecf4fb95..d103c354 100644 --- a/pytest_robotframework/__init__.py +++ b/pytest_robotframework/__init__.py @@ -29,12 +29,16 @@ from basedtyping import Function, P, T from robot import result, running -from robot.api import SuiteVisitor, deco +from robot.api import deco from robot.api.interfaces import ListenerV2, ListenerV3 from robot.libraries.BuiltIn import BuiltIn +from robot.model.visitor import SuiteVisitor from robot.running.librarykeywordrunner import LibraryKeywordRunner from robot.running.statusreporter import StatusReporter -from robot.utils import getshortdoc, printable_name +from robot.utils import ( + getshortdoc, # pyright:ignore[reportUnknownVariableType] + printable_name, # pyright:ignore[reportUnknownVariableType] +) from typing_extensions import Literal, Never, deprecated, override from pytest_robotframework._internal.cringe_globals import current_item, current_session @@ -54,7 +58,9 @@ if TYPE_CHECKING: from types import TracebackType - from robot.running.context import _ExecutionContext + from robot.running.context import ( + _ExecutionContext, # pyright:ignore[reportPrivateUsage] + ) RobotVariables = Dict[str, object] @@ -83,7 +89,9 @@ def import_resource(path: Path | str): to import libraries, use a regular python import""" if execution_context(): - BuiltIn().import_resource(escape_robot_str(str(path))) + BuiltIn().import_resource( # pyright:ignore[reportUnknownMemberType] + escape_robot_str(str(path)) + ) else: _resources.append(Path(path)) @@ -93,7 +101,7 @@ def import_resource(path: Path | str): if robot_6: @patch_method(LibraryKeywordRunner) - def _runner_for( # noqa: PLR0917 + def _runner_for( # pyright:ignore[reportUnusedFunction] # noqa: PLR0917 old_method: Callable[ [ LibraryKeywordRunner, @@ -121,8 +129,8 @@ class _StaticKeyword(StaticKeyword): # pylint:disable=abstract-method """prevents keywords decorated with `pytest_robotframework.keyword` from being wrapped in two status reporters when called from `.robot` tests""" - @override @property + @override def method(self) -> Function: method = cast(Function, super().method) return cast(Function, getattr(method, _kw_attribute, method)) @@ -154,6 +162,7 @@ def __init__( module: str | None = None, doc: str | None = None, ) -> None: + super().__init__() self.name = name self.tags = tags or () self.module = module @@ -176,21 +185,21 @@ def inner( raise if error: raise error - return result_ + # pyright assumes the assignment to error could raise an exception but that will NEVER + # happen + return result_ # pyright:ignore[reportGeneralTypeIssues,reportUnboundVariable] def call(self, fn: Callable[P, T]) -> Callable[P, T]: - # https://github.com/python/mypy/issues/16024 - if isinstance(fn, _KeywordDecorator): # type:ignore[redundant-expr] - return fn # type:ignore[unreachable] + if isinstance(fn, _KeywordDecorator): + return fn keyword_name = self.name or cast( - str, - printable_name( # type:ignore[no-untyped-call] - fn.__name__, code_style=True - ), + str, printable_name(fn.__name__, code_style=True) ) # this doesn't really do anything in python land but we call the original robot keyword # decorator for completeness - deco.keyword(name=keyword_name, tags=self.tags)(fn) + deco.keyword( # pyright:ignore[reportUnknownMemberType] + name=keyword_name, tags=self.tags + )(fn) @wraps(fn) def inner(*args: P.args, **kwargs: P.kwargs) -> T: @@ -203,12 +212,7 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T: context = execution_context() data = running.Keyword(name=keyword_name, args=log_args) doc: str = ( - ( - getshortdoc( # type:ignore[no-untyped-call,no-any-expr] - inspect.getdoc(fn) - ) - or "" - ) + (getshortdoc(inspect.getdoc(fn)) or "") if self.doc is None else self.doc ) @@ -222,12 +226,11 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T: StatusReporter( # type:ignore[assignment] data=data, result=( - # mypy is only run when robot 7 is installed - result.Keyword( # type:ignore[call-arg] - kwname=keyword_name, - libname=self.module, - # https://github.com/KotlinIsland/basedmypy/issues/608 - doc=doc, # type:ignore[no-any-expr] + result.Keyword( + # pyright is only run when robot 7 is installed + kwname=keyword_name, # pyright:ignore[reportGeneralTypeIssues] + libname=self.module, # pyright:ignore[reportGeneralTypeIssues] + doc=doc, args=log_args, tags=self.tags, ) @@ -242,8 +245,7 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T: result=result.Keyword( name=keyword_name, owner=self.module, - # see issue link above - doc=doc, # type:ignore[no-any-expr] + doc=doc, args=log_args, tags=self.tags, ), @@ -315,45 +317,49 @@ def inner( *args: P.args, **kwargs: P.kwargs, ) -> T: - class WrappedContextManager(ContextManager[T]): + T_WrappedContextManager = TypeVar("T_WrappedContextManager") + + class WrappedContextManager(ContextManager[object]): """defers exiting the status reporter until after the wrapped context manager is finished""" def __init__( self, - wrapped: AbstractContextManager[T], + wrapped: AbstractContextManager[T_WrappedContextManager], status_reporter: ContextManager[object], ) -> None: + super().__init__() self.wrapped = wrapped self.status_reporter = status_reporter @override - def __enter__(self) -> T: + # https://github.com/DetachHead/basedpyright/issues/12 + def __enter__(self) -> object: # pyright:ignore[reportMissingSuperCall] # https://github.com/astral-sh/ruff/issues/9499 - self.status_reporter.__enter__() # noqa: PLC2801 + _ = self.status_reporter.__enter__() # noqa: PLC2801 return self.wrapped.__enter__() # noqa: PLC2801 @override - def __exit__( + def __exit__( # pyright:ignore[reportMissingSuperCall] self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /, ) -> bool: + suppress = False try: suppress = self.wrapped.__exit__(exc_type, exc_value, traceback) except BaseException as e: e.__context__ = exc_value exc_value = e - suppress = False raise finally: error = None if suppress else exc_value if error is None: - self.status_reporter.__exit__(None, None, None) + _ = self.status_reporter.__exit__(None, None, None) else: - self.status_reporter.__exit__( + _ = self.status_reporter.__exit__( type(error), error, error.__traceback__ ) return suppress or False @@ -365,8 +371,8 @@ def __exit__( f" {fn_result!r}" ) # 🚀 independently verified for safety by the overloads - return WrappedContextManager( # type:ignore[return-value] - fn_result, status_reporter + return WrappedContextManager( # pyright:ignore[reportGeneralTypeIssues] + cast(AbstractContextManager[object], fn_result), status_reporter ) def __call__( @@ -405,9 +411,11 @@ def keyword( ) -> _FunctionKeywordDecorator: ... -# prevent functions that return Never from matching the context manager overload @overload -def keyword(fn: Callable[P, Never]) -> Callable[P, Never]: ... +# prevent functions that return Never from matching the context manager overload +def keyword( # pyright:ignore[reportOverlappingOverload] + fn: Callable[P, Never] +) -> Callable[P, Never]: ... @deprecated( @@ -425,11 +433,11 @@ def keyword(fn: Callable[P, T]) -> Callable[P, T]: ... def keyword( # pylint:disable=missing-param-doc fn: Callable[P, T] | None = None, *, - name=None, - tags=None, - module=None, - wrap_context_manager=None, -): + name: str | None = None, + tags: tuple[str, ...] | None = None, + module: str | None = None, + wrap_context_manager: bool | None = None, +) -> _KeywordDecorator | Callable[P, T]: """marks a function as a keyword and makes it show in the robot log. unlike robot's `deco.keyword` decorator, this one will make your function appear as a keyword in @@ -457,11 +465,12 @@ def keyword( # pylint:disable=missing-param-doc return _NonWrappedContextManagerKeywordDecorator( name=name, tags=tags, module=module ) - return keyword( # type:ignore[return-value,type-var] - name=name, tags=tags, module=module, wrap_context_manager=wrap_context_manager - )( - fn # type:ignore[arg-type] - ) + return keyword( # pyright:ignore[reportGeneralTypeIssues,reportUnknownVariableType] + name=name, + tags=tags, + module=module, + wrap_context_manager=wrap_context_manager, # pyright:ignore[reportGeneralTypeIssues] + )(fn) def as_keyword( @@ -528,10 +537,8 @@ def keywordify( name=name, tags=tags, module=module, - wrap_context_manager=wrap_context_manager, - )( - getattr(obj, method_name) # type:ignore[no-any-expr] - ), + wrap_context_manager=wrap_context_manager, # pyright:ignore[reportGeneralTypeIssues] + )(getattr(obj, method_name)), ) @@ -579,16 +586,15 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T: # 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__), # type:ignore[no-any-expr] - (staticmethod, classmethod), + inspect.getattr_static(cls, attr.__name__), (staticmethod, classmethod) ) # only wrap methods that are overwritten on the subclass - and attr.__name__ in vars(cls) # type:ignore[no-any-expr] + 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)) # type:ignore[no-any-expr] + setattr(cls, name, wrapped(method)) setattr(cls, marker, True) return cls @@ -626,10 +632,7 @@ def listener(obj: _T_Listener) -> _T_Listener: it gets registered before robot starts running.""" _RobotClassRegistry.check_too_late(obj) _RobotClassRegistry.listeners.append( - obj - if isinstance(obj, (ListenerV2, ListenerV3)) - # https://github.com/python/mypy/issues/16335 - else catch_errors(obj)() # type:ignore[type-var,operator] + obj if isinstance(obj, (ListenerV2, ListenerV3)) else catch_errors(obj)() ) return obj @@ -646,9 +649,6 @@ def pre_rebot_modifier(obj: _T_SuiteVisitor) -> _T_SuiteVisitor: it gets registered before robot starts running.""" _RobotClassRegistry.check_too_late(obj) _RobotClassRegistry.pre_rebot_modifiers.append( - obj - if isinstance(obj, SuiteVisitor) - # https://github.com/python/mypy/issues/16335 - else catch_errors(obj)() # type:ignore[type-var,operator] + obj if isinstance(obj, SuiteVisitor) else catch_errors(obj)() ) return obj diff --git a/pytest_robotframework/_internal/plugin.py b/pytest_robotframework/_internal/plugin.py index 56e4a864..9bc05318 100644 --- a/pytest_robotframework/_internal/plugin.py +++ b/pytest_robotframework/_internal/plugin.py @@ -9,16 +9,16 @@ from pluggy import Result from pytest import TestReport, hookimpl from robot.api import logger -from robot.conf.settings import _BaseSettings +from robot.conf.settings import _BaseSettings # pyright:ignore[reportPrivateUsage] from robot.libraries.BuiltIn import BuiltIn from robot.output import LOGGER from robot.run import RobotFramework -from robot.utils import abspath +from robot.utils import abspath # pyright:ignore[reportUnknownVariableType] from pytest_robotframework import ( - _resources, - _RobotClassRegistry, - _suite_variables, + _resources, # pyright:ignore[reportPrivateUsage] + _RobotClassRegistry, # pyright:ignore[reportPrivateUsage] + _suite_variables, # pyright:ignore[reportPrivateUsage] as_keyword, import_resource, keywordify, @@ -59,16 +59,16 @@ def _collect_slash_run(session: Session, *, collect_only: bool): """ if _RobotClassRegistry.too_late: raise InternalError("somehow ran collect/run twice???") - robot = RobotFramework() # type:ignore[no-untyped-call] + robot = RobotFramework() # need to reset outputdir because if anything from robot gets imported before pytest runs, then # the cwd gets updated, robot will still run with the outdated cwd. # we set it in this wacky way to make sure it never overrides user preferences - _BaseSettings._cli_opts[ # type:ignore[no-untyped-usage,no-any-expr] # noqa: SLF001 + _BaseSettings._cli_opts[ # pyright:ignore[reportUnknownMemberType,reportPrivateUsage] "OutputDir" - ] = ( # type:ignore[no-any-expr] + ] = ( "outputdir", - abspath("."), # type:ignore[no-untyped-call,no-any-expr] + abspath("."), ) robot_arg_list: list[str] = [] @@ -77,24 +77,22 @@ def _collect_slash_run(session: Session, *, collect_only: bool): ) robot_args = cast( Dict[str, object], - always_merger.merge( # type:ignore[no-untyped-call] + always_merger.merge( # pyright:ignore[reportUnknownMemberType] # https://github.com/psf/black/issues/4036 # fmt:off - robot.parse_arguments( # type:ignore[no-untyped-call] - [ # type:ignore[no-any-expr] + robot.parse_arguments( # pyright:ignore[reportUnknownMemberType,reportUnknownArgumentType] + [ *robot_arg_list, # not actually used here, but the argument parser requires at least one path session.path, ] )[0], # fmt:on - { # type:ignore[no-any-expr] + { "extension": "py:robot", "runemptysuite": True, - "parser": [PythonParser(session)], # type:ignore[no-any-expr] - "prerunmodifier": [ # type:ignore[no-any-expr] - PytestCollector(session, collect_only=collect_only) - ], + "parser": [PythonParser(session)], + "prerunmodifier": [PytestCollector(session, collect_only=collect_only)], } ), ) @@ -107,14 +105,12 @@ def _collect_slash_run(session: Session, *, collect_only: bool): "exitonerror": True, } else: - listener(PytestRuntestProtocolHooks(session)) - listener(ErrorDetector(session)) - robot_args = always_merger.merge( # type:ignore[no-untyped-call] + _ = listener(PytestRuntestProtocolHooks(session)) + _ = listener(ErrorDetector(session)) + robot_args = always_merger.merge( # pyright:ignore[reportUnknownMemberType,reportUnknownVariableType] robot_args, - { # type:ignore[no-any-expr] - "prerunmodifier": [ # type:ignore[no-any-expr] - PytestRuntestProtocolInjector(session) - ], + { + "prerunmodifier": [PytestRuntestProtocolInjector(session)], "listener": _RobotClassRegistry.listeners, "prerebotmodifier": _RobotClassRegistry.pre_rebot_modifiers, }, @@ -125,8 +121,8 @@ def _collect_slash_run(session: Session, *, collect_only: bool): # LOGGER is needed for log_file listener methods to prevent logger from deactivating after # the test is over with LOGGER: - robot.main( # type:ignore[no-untyped-call] - [session.path], # type:ignore[no-any-expr] + _ = robot.main( # pyright:ignore[reportUnknownMemberType,reportUnknownVariableType] + [session.path], # needed because PythonParser.visit_init creates an empty suite **robot_args, ) @@ -160,12 +156,7 @@ def pytest_addoption(parser: Parser): def pytest_robot_modify_args(args: list[str], session: Session): - result = cast( - str, - session.config.getoption( # type:ignore[no-untyped-call] - "--robotargs" - ), - ) + result = cast(str, session.config.getoption("--robotargs")) if result: # i saw some code that uses session.config.issue_config_time_warning but that doesnt work # who knows why @@ -177,14 +168,14 @@ def pytest_robot_modify_args(args: list[str], session: Session): args.extend(result.split(" ")) -@hookimpl(tryfirst=True) # type:ignore[no-any-expr] +@hookimpl(tryfirst=True) def pytest_sessionstart(session: Session): - cringe_globals._current_session = session # noqa: SLF001 + cringe_globals._current_session = session # pyright:ignore[reportPrivateUsage] -@hookimpl(trylast=True) # type:ignore[no-any-expr] +@hookimpl(trylast=True) def pytest_sessionfinish(): - cringe_globals._current_session = None # noqa: SLF001 + cringe_globals._current_session = None # pyright:ignore[reportPrivateUsage] def pytest_assertion_pass(orig: str, expl: str): @@ -192,7 +183,7 @@ def pytest_assertion_pass(orig: str, expl: str): # this matches what's logged if an assertion fails, so we keep it the same here for consistency # (idk why there's no pytest_assertion_fail hook, only reprcompare which is different) with as_keyword("assert", args=[orig]): - logger.info(expl) # type:ignore[no-untyped-call] + logger.info(expl) def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: @@ -208,7 +199,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | def pytest_collection(session: Session) -> object: - if session.config.option.collectonly: # type:ignore[no-any-expr] + if session.config.option.collectonly: _collect_slash_run(session, collect_only=True) return True @@ -221,7 +212,7 @@ def pytest_collect_file(parent: Collector, file_path: Path) -> Collector | None: return None -@hookimpl(hookwrapper=True) # type:ignore[no-any-expr] +@hookimpl(hookwrapper=True) def pytest_runtest_setup(item: Item) -> HookImplResult: if not isinstance(item, RobotItem): # `set_variables` and `import_resource` is only supported in python files. @@ -229,7 +220,7 @@ def pytest_runtest_setup(item: Item) -> HookImplResult: # section and resources should be imported with `Resource` in the `*** Settings***` section builtin = BuiltIn() for key, value in _suite_variables[item.path].items(): - builtin.set_suite_variable( + builtin.set_suite_variable( # pyright:ignore[reportUnknownMemberType] r"${" + key + "}", escape_robot_str(value) if isinstance(value, str) else value, ) @@ -240,20 +231,20 @@ def pytest_runtest_setup(item: Item) -> HookImplResult: save_exception_to_item(item, result) -@hookimpl(hookwrapper=True) # type:ignore[no-any-expr] +@hookimpl(hookwrapper=True) def pytest_runtest_call(item: Item) -> HookImplResult: result = yield save_exception_to_item(item, result) -@hookimpl(hookwrapper=True) # type:ignore[no-any-expr] +@hookimpl(hookwrapper=True) def pytest_runtest_teardown(item: Item) -> HookImplResult: result = yield save_exception_to_item(item, result) def pytest_runtestloop(session: Session) -> object: - if session.config.option.collectonly: # type:ignore[no-any-expr] + if session.config.option.collectonly: return None # TODO: should probably keywordify skip as well, but it messes with the handling in robot_library # https://github.com/DetachHead/pytest-robotframework/issues/51 diff --git a/pytest_robotframework/_internal/pytest_robot_items.py b/pytest_robotframework/_internal/pytest_robot_items.py index 37bea72a..d23c156a 100644 --- a/pytest_robotframework/_internal/pytest_robot_items.py +++ b/pytest_robotframework/_internal/pytest_robot_items.py @@ -37,10 +37,13 @@ class RobotFile(File): def collect(self) -> Iterable[Item]: for test in cast( Iterator[ModelTestCase], - self.session.stash[collected_robot_suite_key].all_tests, + # https://github.com/robotframework/robotframework/issues/4940#issuecomment-1817683893 + self.session.stash[ # pyright:ignore[reportUnknownMemberType] + collected_robot_suite_key + ].all_tests, ): if self.path == test.source: - yield RobotItem.from_parent( # type:ignore[no-untyped-call,no-any-expr] + yield RobotItem.from_parent( # pyright:ignore[reportUnknownMemberType] self, name=test.name, robot_test=test ) @@ -57,7 +60,7 @@ def __init__( nodeid: str | None = None, **kwargs: object, ): - super().__init__( + super().__init__( # pyright:ignore[reportUnknownMemberType] name=name, parent=parent, config=config, @@ -92,14 +95,16 @@ def _check_skipped() -> Iterator[None]: try: yield except ExecutionFailed as e: - if e.status == "SKIP": # type:ignore[no-any-expr] - skip(e.message) # type:ignore[no-any-expr] + if e.status == "SKIP": + skip(e.message) raise def _run_keyword(self, keyword: model.Keyword | None): if keyword: with self._check_skipped(): - BuiltIn().run_keyword(keyword.name, *keyword.args) + BuiltIn().run_keyword( # pyright:ignore[reportUnknownMemberType] + keyword.name, *keyword.args + ) @override def setup(self): @@ -114,22 +119,22 @@ def runtest(self): check_skipped = self._check_skipped() if robot_6: with check_skipped: - # mypy is only run when robot 7 is installed - BodyRunner( # type:ignore[call-arg] + # pyright is only run when robot 7 is installed + BodyRunner( # pyright:ignore[reportUnknownMemberType,reportGeneralTypeIssues] context=context, templated=bool(test.template) - ).run( # type:ignore[no-untyped-call] + ).run( self.stash[original_body_key] ) else: wrapped_body = test.body - # body uses robot's setter decorator which doesn't work with mypy - test.body = self.stash[original_body_key] # type:ignore[index] + test.body = self.stash[original_body_key] try: with check_skipped: - BodyRunner( + BodyRunner( # pyright:ignore[reportUnknownMemberType] context=context, templated=bool(test.template) - ).run( # type:ignore[no-untyped-call] - data=test, result=context.test + ).run( + data=test, + result=context.test, # pyright:ignore[reportUnknownMemberType] ) finally: test.body = wrapped_body @@ -139,7 +144,6 @@ def teardown(self): self._run_keyword(self.stash[original_teardown_key]) @override - # https://github.com/microsoft/pyright/issues/6157 - def reportinfo(self) -> (PathLike[str] | str, int | None, str): # pyright:ignore + def reportinfo(self) -> tuple[PathLike[str] | str, int | None, str]: line_number = self.stash[running_test_case_key].lineno return (self.path, None if line_number is None else line_number - 1, self.name) diff --git a/pytest_robotframework/_internal/robot_classes.py b/pytest_robotframework/_internal/robot_classes.py index cf42ea14..b3a8c789 100644 --- a/pytest_robotframework/_internal/robot_classes.py +++ b/pytest_robotframework/_internal/robot_classes.py @@ -9,11 +9,11 @@ from _pytest import runner from pluggy import HookImpl -from pluggy._hooks import _SubsetHookCaller +from pluggy._hooks import _SubsetHookCaller # pyright:ignore[reportPrivateUsage] from pytest import Function, Item, Session, StashKey from robot import model, result, running -from robot.api import SuiteVisitor -from robot.api.interfaces import ListenerV3, Parser, TestDefaults +from robot.api.interfaces import ListenerV3, Parser +from robot.model import SuiteVisitor from robot.running.model import Body from typing_extensions import override @@ -32,6 +32,7 @@ from pathlib import Path from basedtyping import P + from robot.running.builder.settings import TestDefaults def _create_running_keyword( @@ -42,9 +43,7 @@ def _create_running_keyword( ) -> running.Keyword: """creates a `running.Keyword` for the specified keyword from `_robot_library`""" if kwargs: - raise InternalError( - f"kwargs not supported: {kwargs}" # type:ignore[helpful-string] - ) + raise InternalError(f"kwargs not supported: {kwargs}") return running.Keyword( name=f"{fn.__module__}.{fn.__name__}", args=args, type=keyword_type ) @@ -89,7 +88,7 @@ def parse(self, source: Path, defaults: TestDefaults) -> running.TestSuite: ), ) ] - suite.tests.append(test_case) + _ = suite.tests.append(test_case) return suite @override @@ -120,6 +119,7 @@ class PytestCollector(SuiteVisitor): """ def __init__(self, session: Session, *, collect_only: bool): + super().__init__() self.session = session self.collect_only = collect_only self.collection_error: Exception | None = None @@ -133,7 +133,7 @@ def visit_suite(self, suite: ModelTestSuite): if not suite.parent: self.session.stash[collected_robot_suite_key] = suite try: - self.session.perform_collect() + _ = self.session.perform_collect() except Exception as e: # noqa: BLE001 # if collection fails we still need to clean up the suite (ie. delete all the fake # tests), so we defer the error to `end_suite` for the top level suite @@ -147,7 +147,11 @@ def visit_suite(self, suite: ModelTestSuite): continue test_case = running.TestCase( name=item.name, - doc=cast(Function, item.function).__doc__ or "", + doc=cast( + Function, + item.function, # pyright:ignore[reportUnknownMemberType] + ).__doc__ + or "", tags=[ ":".join([ marker.name, @@ -162,7 +166,7 @@ def visit_suite(self, suite: ModelTestSuite): test_case.body = Body() item.stash[running_test_case_key] = test_case if self.collect_only: - suite.tests.clear() # type:ignore[no-untyped-call] + suite.tests.clear() else: # remove any .robot tests that were filtered out by pytest (and the fake test # from `PythonParser`): @@ -177,8 +181,9 @@ def visit_suite(self, suite: ModelTestSuite): if module.__doc__ and not suite.doc: suite.doc = module.__doc__ if item.path == suite.source: - suite.tests.append(item.stash[running_test_case_key]) - super().visit_suite(suite) + _ = suite.tests.append(item.stash[running_test_case_key]) + # https://github.com/robotframework/robotframework/issues/4940 + super().visit_suite(suite) # pyright:ignore[reportUnknownMemberType] @override def end_suite(self, suite: ModelTestSuite): @@ -212,24 +217,24 @@ class PytestRuntestProtocolInjector(SuiteVisitor): """ def __init__(self, session: Session): + super().__init__() self.session = session @override def start_suite(self, suite: ModelTestSuite): if not isinstance(suite, running.TestSuite): raise _NotRunningTestSuiteError - suite.resource.imports.library( + _ = suite.resource.imports.library( robot_library.__name__, alias=robot_library.__name__ ) - # https://github.com/KotlinIsland/basedmypy/issues/568 - item = cast(Optional[Item], None) + item: Item | None = None for test in suite.tests: previous_item: Item | None = item item = get_item_from_robot_test(self.session, test) # need to set nextitem on all the items, because for some reason the attribute exists on # the class but is never used if previous_item and not cast(Optional[Item], previous_item.nextitem): - previous_item.nextitem = item + previous_item.nextitem = item # pyright:ignore[reportGeneralTypeIssues] if not item: raise InternalError( "this should NEVER happen, `PytestCollector` failed to filter out" @@ -268,6 +273,7 @@ class PytestRuntestProtocolHooks(ListenerV3): """ def __init__(self, session: Session): + super().__init__() self.session = session self.stop_running_hooks = False self.hookwrappers: dict[HookImpl, _HookWrapper] = {} @@ -290,15 +296,25 @@ def _call_hooks(item: Item, hookimpls: list[HookImpl]) -> object: try: # can't use the public get_hookimpls method because it returns a copy and we need to # mutate the original - hook_caller._hookimpls[:] = [] # noqa: SLF001 + # https://github.com/microsoft/pyright/issues/6994 + # https://github.com/psf/black/issues/3946 + # fmt: off + hook_caller._hookimpls[:] = ( # pyright:ignore[reportPrivateUsage,reportUnknownArgumentType] + [] + ) + # fmt: on for hookimpl in hookimpls: - hook_caller._add_hookimpl(hookimpl) # noqa: SLF001 + hook_caller._add_hookimpl( # pyright:ignore[reportPrivateUsage] + hookimpl + ) hook_result = cast( object, hook_caller(item=item, nextitem=cast(Optional[Item], item.nextitem)), ) finally: - hook_caller._hookimpls[:] = original_hookimpls # noqa: SLF001 + hook_caller._hookimpls[:] = ( # pyright:ignore[reportPrivateUsage] + original_hookimpls + ) return hook_result @override @@ -315,12 +331,14 @@ def start_test( original_hook_caller = ( # need to bypass the _SubsetHookCaller proxy otherwise it won't actually remove the # plugin - hook_caller._orig # noqa: SLF001 + hook_caller._orig # pyright:ignore[reportPrivateUsage] if isinstance(hook_caller, _SubsetHookCaller) else hook_caller ) with suppress(ValueError): # already been removed - original_hook_caller._remove_plugin(runner) # noqa: SLF001 + original_hook_caller._remove_plugin( # pyright:ignore[reportPrivateUsage] + runner + ) def enter_wrapper(hook: HookImpl, item: Item, nextitem: Item): """calls the first half of a hookwrapper""" @@ -355,8 +373,10 @@ def exit_wrapper(hook: HookImpl) -> object: HookImpl( hook.plugin, hook.plugin_name, - lambda item, nextitem, *, hook=hook: enter_wrapper( - hook, item, nextitem + lambda item, nextitem, *, hook=hook: enter_wrapper( # pyright:ignore[reportUnknownArgumentType,reportUnknownLambdaType] + hook, + item, # pyright:ignore[reportUnknownArgumentType] + nextitem, # pyright:ignore[reportUnknownArgumentType] ), { **hook.opts, @@ -371,7 +391,9 @@ def exit_wrapper(hook: HookImpl) -> object: HookImpl( hook.plugin, hook.plugin_name, - lambda hook=hook: exit_wrapper(hook), + lambda hook=hook: exit_wrapper( # pyright:ignore[reportUnknownArgumentType,reportUnknownLambdaType] + hook # pyright:ignore[reportUnknownArgumentType] + ), { **hook.opts, "hookwrapper": False, @@ -405,7 +427,7 @@ def end_test( if self.stop_running_hooks: self.stop_running_hooks = False # for next time else: - self._call_hooks(item, self.end_test_hooks) + _ = self._call_hooks(item, self.end_test_hooks) # remove all the hooks since they need to be re-evaluated for each item, since items can # have different hooks depending on what conftest files are nearby: @@ -421,6 +443,7 @@ class ErrorDetector(ListenerV3): """ def __init__(self, session: Session) -> None: + super().__init__() self.session = session self.current_test: running.TestCase | None = None diff --git a/pytest_robotframework/_internal/robot_library.py b/pytest_robotframework/_internal/robot_library.py index d1fdd32b..035deb25 100644 --- a/pytest_robotframework/_internal/robot_library.py +++ b/pytest_robotframework/_internal/robot_library.py @@ -6,7 +6,10 @@ from typing import TYPE_CHECKING, Final, List, Literal from _pytest._code.code import TerminalRepr -from _pytest.runner import call_and_report, show_test_item +from _pytest.runner import ( + call_and_report, # pyright:ignore[reportUnknownVariableType] + show_test_item, +) from pytest import Item, StashKey, TestReport from robot.libraries.BuiltIn import BuiltIn @@ -30,14 +33,12 @@ def _call_and_report_robot_edition( """wrapper for the `call_and_report` function used by `_pytest.runner.runtestprotocol` with additional logic to show the result in the robot log""" reports: list[TestReport] = init_stash(_report_key, list, item) - report = call_and_report( # type:ignore[no-untyped-call] - item, when, log=True, **kwargs - ) + report = call_and_report(item, when, log=True, **kwargs) reports.append(report) if report.skipped: # empty string means xfail with no reason, None means it was not an xfail xfail_reason = report.wasxfail if hasattr(report, "wasxfail") else None - BuiltIn().skip( # type:ignore[no-untyped-call] + BuiltIn().skip( # TODO: is there a reliable way to get the reason when skipped by a skip/skipif marker? # https://github.com/DetachHead/pytest-robotframework/issues/51 "" @@ -64,15 +65,15 @@ def _call_and_report_robot_edition( @keyword def setup(arg: Cloaked[Item]): item = arg.value - cringe_globals._current_item = item # noqa: SLF001 + cringe_globals._current_item = item # pyright:ignore[reportPrivateUsage] # mostly copied from the start of `_pytest.runner.runtestprotocol`: if ( hasattr(item, "_request") - and not item._request # type: ignore[no-any-expr] # noqa: SLF001 + and not item._request # pyright:ignore[reportGeneralTypeIssues,reportUnknownMemberType] ): # This only happens if the item is re-run, as is done by # pytest-rerunfailures. - item._initrequest() # type: ignore[attr-defined] # noqa: SLF001 + item._initrequest() # pyright:ignore[reportGeneralTypeIssues,reportUnknownMemberType] _call_and_report_robot_edition(item, "setup") @@ -82,12 +83,12 @@ def run_test(arg: Cloaked[Item]): # mostly copied from the middle of `_pytest.runner.runtestprotocol`: reports = item.stash[_report_key] if reports[0].passed: - if item.config.getoption( # type:ignore[no-any-expr,no-untyped-call] - "setupshow", default=False + if item.config.getoption( + "setupshow", default=False # pyright:ignore[reportGeneralTypeIssues] ): show_test_item(item) - if not item.config.getoption( # type:ignore[no-any-expr,no-untyped-call] - "setuponly", default=False + if not item.config.getoption( + "setuponly", default=False # pyright:ignore[reportGeneralTypeIssues] ): _call_and_report_robot_edition(item, "call") @@ -96,10 +97,8 @@ def run_test(arg: Cloaked[Item]): def teardown(arg: Cloaked[Item]): item = arg.value # mostly copied from the end of `_pytest.runner.runtestprotocol`: - _call_and_report_robot_edition( - item, "teardown", nextitem=item.nextitem # type:ignore[no-any-expr] - ) - cringe_globals._current_item = None # noqa: SLF001 + _call_and_report_robot_edition(item, "teardown", nextitem=item.nextitem) + cringe_globals._current_item = None # pyright:ignore[reportPrivateUsage] @keyword diff --git a/pytest_robotframework/_internal/robot_utils.py b/pytest_robotframework/_internal/robot_utils.py index 88e033a9..a20de0dd 100644 --- a/pytest_robotframework/_internal/robot_utils.py +++ b/pytest_robotframework/_internal/robot_utils.py @@ -5,7 +5,9 @@ from basedtyping import T from pytest import Item, Session, StashKey from robot import model, running -from robot.running.context import _ExecutionContext +from robot.running.context import ( + _ExecutionContext, # pyright:ignore[reportPrivateUsage] +) from robot.version import VERSION from typing_extensions import override @@ -21,6 +23,7 @@ class Cloaked(Generic[T]): """allows you to pass arguments to robot keywords without them appearing in the log""" def __init__(self, value: T): + super().__init__() self.value = value @override @@ -32,7 +35,10 @@ def execution_context() -> _ExecutionContext | None: # need to import it every time because it changes from robot.running import EXECUTION_CONTEXTS # noqa: PLC0415 - return cast(Union[_ExecutionContext, None], EXECUTION_CONTEXTS.current) + return cast( + Union[_ExecutionContext, None], + EXECUTION_CONTEXTS.current, # pyright:ignore[reportUnknownMemberType] + ) running_test_case_key = StashKey[running.TestCase]() diff --git a/tests/conftest.py b/tests/conftest.py index df2f626e..c1ff7ce4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ def pytester_dir(pytester: Pytester, request: FixtureRequest) -> PytesterDir: `tests/fixtures/[test file]/[test name].py` to the pytester temp dir for the current test, so you don't have to write your test files as strings with the `makefile`/`makepyfile` methods """ - test = cast(Function, request.node) + test = cast(Function, request.node) # pyright:ignore[reportUnknownMemberType] test_name = test.name fixtures_folder = Path(__file__).parent / "fixtures" test_file_fixture_dir = ( diff --git a/tests/fixtures/test_python/test_as_keyword_context_manager_try_except.py b/tests/fixtures/test_python/test_as_keyword_context_manager_try_except.py index 6755b3f2..bb63e09b 100644 --- a/tests/fixtures/test_python/test_as_keyword_context_manager_try_except.py +++ b/tests/fixtures/test_python/test_as_keyword_context_manager_try_except.py @@ -14,4 +14,4 @@ def test_foo(): with as_keyword("hi"): raise FooError # noqa: TRY301 except FooError: - logger.info("2") # type:ignore[no-untyped-call] + logger.info("2") diff --git a/tests/fixtures/test_python/test_error_moment.py b/tests/fixtures/test_python/test_error_moment.py index b0d3befe..2e51487e 100644 --- a/tests/fixtures/test_python/test_error_moment.py +++ b/tests/fixtures/test_python/test_error_moment.py @@ -4,5 +4,5 @@ def test_foo(): - logger.error("foo") # type:ignore[no-untyped-call] - logger.info("bar") # type:ignore[no-untyped-call] + logger.error("foo") + logger.info("bar") diff --git a/tests/fixtures/test_python/test_error_moment_and_second_test.py b/tests/fixtures/test_python/test_error_moment_and_second_test.py index c61f4e0b..53314ec5 100644 --- a/tests/fixtures/test_python/test_error_moment_and_second_test.py +++ b/tests/fixtures/test_python/test_error_moment_and_second_test.py @@ -4,8 +4,8 @@ def test_foo(): - logger.error("foo") # type:ignore[no-untyped-call] + logger.error("foo") def test_bar(): - logger.info("bar") # type:ignore[no-untyped-call] + logger.info("bar") diff --git a/tests/fixtures/test_python/test_error_moment_exitonerror.py b/tests/fixtures/test_python/test_error_moment_exitonerror.py index b0d3befe..2e51487e 100644 --- a/tests/fixtures/test_python/test_error_moment_exitonerror.py +++ b/tests/fixtures/test_python/test_error_moment_exitonerror.py @@ -4,5 +4,5 @@ def test_foo(): - logger.error("foo") # type:ignore[no-untyped-call] - logger.info("bar") # type:ignore[no-untyped-call] + logger.error("foo") + logger.info("bar") diff --git a/tests/fixtures/test_python/test_error_moment_exitonerror_multiple_tests.py b/tests/fixtures/test_python/test_error_moment_exitonerror_multiple_tests.py index 4b7fe267..7b31d324 100644 --- a/tests/fixtures/test_python/test_error_moment_exitonerror_multiple_tests.py +++ b/tests/fixtures/test_python/test_error_moment_exitonerror_multiple_tests.py @@ -4,9 +4,9 @@ def test_foo(): - logger.error("foo") # type:ignore[no-untyped-call] - logger.info("bar") # type:ignore[no-untyped-call] + logger.error("foo") + logger.info("bar") def test_bar(): - logger.info("baz") # type:ignore[no-untyped-call] + logger.info("baz") diff --git a/tests/fixtures/test_python/test_error_moment_setup/conftest.py b/tests/fixtures/test_python/test_error_moment_setup/conftest.py index 6ffd9ffb..fbf1452c 100644 --- a/tests/fixtures/test_python/test_error_moment_setup/conftest.py +++ b/tests/fixtures/test_python/test_error_moment_setup/conftest.py @@ -4,5 +4,5 @@ def pytest_runtest_setup(): - logger.error("foo") # type:ignore[no-untyped-call] - logger.info("bar") # type:ignore[no-untyped-call] + logger.error("foo") + logger.info("bar") diff --git a/tests/fixtures/test_python/test_error_moment_setup/test_foo.py b/tests/fixtures/test_python/test_error_moment_setup/test_foo.py index 630377f0..c0a87396 100644 --- a/tests/fixtures/test_python/test_error_moment_setup/test_foo.py +++ b/tests/fixtures/test_python/test_error_moment_setup/test_foo.py @@ -4,4 +4,4 @@ def test_foo(): - logger.info("baz") # type:ignore[no-untyped-call] + logger.info("baz") diff --git a/tests/fixtures/test_python/test_error_moment_teardown/conftest.py b/tests/fixtures/test_python/test_error_moment_teardown/conftest.py index cada28f9..2dd29385 100644 --- a/tests/fixtures/test_python/test_error_moment_teardown/conftest.py +++ b/tests/fixtures/test_python/test_error_moment_teardown/conftest.py @@ -4,5 +4,5 @@ def pytest_runtest_teardown(): - logger.error("foo") # type:ignore[no-untyped-call] - logger.info("bar") # type:ignore[no-untyped-call] + logger.error("foo") + logger.info("bar") diff --git a/tests/fixtures/test_python/test_error_moment_teardown/test_foo.py b/tests/fixtures/test_python/test_error_moment_teardown/test_foo.py index 630377f0..c0a87396 100644 --- a/tests/fixtures/test_python/test_error_moment_teardown/test_foo.py +++ b/tests/fixtures/test_python/test_error_moment_teardown/test_foo.py @@ -4,4 +4,4 @@ def test_foo(): - logger.info("baz") # type:ignore[no-untyped-call] + logger.info("baz") diff --git a/tests/fixtures/test_python/test_fixture_scope.py b/tests/fixtures/test_python/test_fixture_scope.py index 22eb4b37..79ee569f 100644 --- a/tests/fixtures/test_python/test_fixture_scope.py +++ b/tests/fixtures/test_python/test_fixture_scope.py @@ -7,13 +7,14 @@ @fixture(scope="class") -def _fixture_1(): +# https://github.com/DetachHead/basedpyright/issues/22 +def _fixture_1(): # pyright:ignore[reportUnusedFunction] global count_1 count_1 += 1 @fixture() -def _fixture_2(_fixture_1: None): +def _fixture_2(_fixture_1: None): # pyright:ignore[reportUnusedFunction] global count_2 count_2 += 1 diff --git a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_doesnt_suppress.py b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_doesnt_suppress.py index f7cf9b31..ef5a0b4b 100644 --- a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_doesnt_suppress.py +++ b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_doesnt_suppress.py @@ -14,15 +14,15 @@ @keyword(wrap_context_manager=True) @contextmanager def asdf() -> Iterator[None]: - logger.info("start") # type:ignore[no-untyped-call] + logger.info("start") try: yield finally: - logger.info("end") # type:ignore[no-untyped-call] + logger.info("end") def test_foo(): with asdf(): - logger.info("0") # type:ignore[no-untyped-call] + logger.info("0") raise Exception - logger.info("1") # type:ignore[unreachable] + logger.info("1") diff --git a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_body_and_exit.py b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_body_and_exit.py index f35cdd38..1b4d1d85 100644 --- a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_body_and_exit.py +++ b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_body_and_exit.py @@ -14,7 +14,7 @@ @keyword(wrap_context_manager=True) @contextmanager def asdf() -> Iterator[None]: - logger.info("start") # type:ignore[no-untyped-call] + logger.info("start") try: yield finally: @@ -24,4 +24,4 @@ def asdf() -> Iterator[None]: def test_foo(): with asdf(): raise Exception("fdsa") - logger.info(1) # type:ignore[unreachable] + logger.info(1) diff --git a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_exit.py b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_exit.py index f43b728f..7c180719 100644 --- a/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_exit.py +++ b/tests/fixtures/test_python/test_keyword_decorator_context_manager_that_raises_in_exit.py @@ -14,12 +14,12 @@ @keyword(wrap_context_manager=True) @contextmanager def asdf() -> Iterator[None]: - logger.info("start") # type:ignore[no-untyped-call] + logger.info("start") yield raise Exception("asdf") def test_foo(): with asdf(): - logger.info("0") # type:ignore[no-untyped-call] - logger.info("1") # type:ignore[no-untyped-call] + logger.info("0") + logger.info("1") diff --git a/tests/fixtures/test_python/test_keyword_decorator_returns_context_manager_that_isnt_used.py b/tests/fixtures/test_python/test_keyword_decorator_returns_context_manager_that_isnt_used.py index 40665ee5..9a0032e6 100644 --- a/tests/fixtures/test_python/test_keyword_decorator_returns_context_manager_that_isnt_used.py +++ b/tests/fixtures/test_python/test_keyword_decorator_returns_context_manager_that_isnt_used.py @@ -14,12 +14,12 @@ @keyword(wrap_context_manager=True) @contextmanager def asdf() -> Iterator[None]: - logger.info("start") # type:ignore[no-untyped-call] + logger.info("start") try: yield finally: - logger.info("end") # type:ignore[no-untyped-call] + logger.info("end") def test_foo(): - asdf() + _ = asdf() diff --git a/tests/fixtures/test_python/test_keyword_decorator_try_except.py b/tests/fixtures/test_python/test_keyword_decorator_try_except.py index 36963ad0..1c52fa0f 100644 --- a/tests/fixtures/test_python/test_keyword_decorator_try_except.py +++ b/tests/fixtures/test_python/test_keyword_decorator_try_except.py @@ -18,4 +18,4 @@ def test_foo(): try: bar() except FooError: - logger.info("hi") # type:ignore[no-untyped-call] + logger.info("hi") diff --git a/tests/fixtures/test_python/test_keywordify_keyword_inside_context_manager.py b/tests/fixtures/test_python/test_keywordify_keyword_inside_context_manager.py index d09c9b0a..fc40632e 100644 --- a/tests/fixtures/test_python/test_keywordify_keyword_inside_context_manager.py +++ b/tests/fixtures/test_python/test_keywordify_keyword_inside_context_manager.py @@ -8,7 +8,7 @@ @keyword def asdf(): - logger.info("1") # type:ignore[no-untyped-call] + logger.info("1") def test_foo(): diff --git a/tests/fixtures/test_python/test_listener_calls_log_file/Listener.py b/tests/fixtures/test_python/test_listener_calls_log_file/Listener.py index 2c7a8269..b24229f7 100644 --- a/tests/fixtures/test_python/test_listener_calls_log_file/Listener.py +++ b/tests/fixtures/test_python/test_listener_calls_log_file/Listener.py @@ -13,4 +13,4 @@ class Listener(ListenerV3): def log_file(self, path: Path): # TODO: this doesnt log to the console so no other way to verify that it ran # https://github.com/DetachHead/pytest-robotframework/issues/39 - Path("hi").write_text("") + _ = Path("hi").write_text("") diff --git a/tests/fixtures/test_python/test_pytest_runtest_protocol_item_hook/foo/conftest.py b/tests/fixtures/test_python/test_pytest_runtest_protocol_item_hook/foo/conftest.py index dd31c03a..d3a8295b 100644 --- a/tests/fixtures/test_python/test_pytest_runtest_protocol_item_hook/foo/conftest.py +++ b/tests/fixtures/test_python/test_pytest_runtest_protocol_item_hook/foo/conftest.py @@ -7,7 +7,7 @@ run_key = StashKey[int]() -@hookimpl(hookwrapper=True) # type:ignore[no-any-expr] +@hookimpl(hookwrapper=True) def pytest_runtest_protocol(item: Item) -> Iterator[None]: item.session.stash[run_key] = 1 yield diff --git a/tests/fixtures/test_python/test_pytest_runtest_protocol_session_hook/conftest.py b/tests/fixtures/test_python/test_pytest_runtest_protocol_session_hook/conftest.py index dd31c03a..d3a8295b 100644 --- a/tests/fixtures/test_python/test_pytest_runtest_protocol_session_hook/conftest.py +++ b/tests/fixtures/test_python/test_pytest_runtest_protocol_session_hook/conftest.py @@ -7,7 +7,7 @@ run_key = StashKey[int]() -@hookimpl(hookwrapper=True) # type:ignore[no-any-expr] +@hookimpl(hookwrapper=True) def pytest_runtest_protocol(item: Item) -> Iterator[None]: item.session.stash[run_key] = 1 yield diff --git a/tests/fixtures/test_python/test_robot_keyword_in_python_test/test_foo.py b/tests/fixtures/test_python/test_robot_keyword_in_python_test/test_foo.py index 000b6471..46568d38 100644 --- a/tests/fixtures/test_python/test_robot_keyword_in_python_test/test_foo.py +++ b/tests/fixtures/test_python/test_robot_keyword_in_python_test/test_foo.py @@ -10,4 +10,4 @@ def test_foo(): - BuiltIn().run_keyword("bar") + BuiltIn().run_keyword("bar") # pyright:ignore[reportUnknownMemberType] diff --git a/tests/fixtures/test_python/test_setup_passes/conftest.py b/tests/fixtures/test_python/test_setup_passes/conftest.py index 53098658..b3416e82 100644 --- a/tests/fixtures/test_python/test_setup_passes/conftest.py +++ b/tests/fixtures/test_python/test_setup_passes/conftest.py @@ -4,4 +4,4 @@ def pytest_runtest_setup(): - logger.info("2") # type:ignore[no-untyped-call] + logger.info("2") diff --git a/tests/fixtures/test_python/test_setup_passes/test_foo.py b/tests/fixtures/test_python/test_setup_passes/test_foo.py index 5302307a..8876099b 100644 --- a/tests/fixtures/test_python/test_setup_passes/test_foo.py +++ b/tests/fixtures/test_python/test_setup_passes/test_foo.py @@ -4,4 +4,4 @@ def test_one_test_robot2(): - logger.info("1") # type:ignore[no-untyped-call] + logger.info("1") diff --git a/tests/fixtures/test_python/test_teardown_passes/conftest.py b/tests/fixtures/test_python/test_teardown_passes/conftest.py index 954c1f76..d0f3ebc3 100644 --- a/tests/fixtures/test_python/test_teardown_passes/conftest.py +++ b/tests/fixtures/test_python/test_teardown_passes/conftest.py @@ -4,4 +4,4 @@ def pytest_runtest_teardown(): - logger.info("2") # type:ignore[no-untyped-call] + logger.info("2") diff --git a/tests/fixtures/test_python/test_teardown_passes/test_foo.py b/tests/fixtures/test_python/test_teardown_passes/test_foo.py index 8d0320f4..e08ba441 100644 --- a/tests/fixtures/test_python/test_teardown_passes/test_foo.py +++ b/tests/fixtures/test_python/test_teardown_passes/test_foo.py @@ -4,4 +4,4 @@ def test_one_test_robot(): - logger.info("1") # type:ignore[no-untyped-call] + logger.info("1") diff --git a/tests/fixtures/test_robot/test_keyword_decorator/foo.py b/tests/fixtures/test_robot/test_keyword_decorator/foo.py index 0b896c01..0b711a27 100644 --- a/tests/fixtures/test_robot/test_keyword_decorator/foo.py +++ b/tests/fixtures/test_robot/test_keyword_decorator/foo.py @@ -8,4 +8,4 @@ @keyword def bar(): - logger.info("1") # type:ignore[no-untyped-call] + logger.info("1") diff --git a/tests/fixtures/test_robot/test_keyword_decorator_and_other_decorator/foo.py b/tests/fixtures/test_robot/test_keyword_decorator_and_other_decorator/foo.py index 63acef17..d4763d7c 100644 --- a/tests/fixtures/test_robot/test_keyword_decorator_and_other_decorator/foo.py +++ b/tests/fixtures/test_robot/test_keyword_decorator_and_other_decorator/foo.py @@ -23,4 +23,4 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> T: @decorator @keyword def bar(): - logger.info("1") # type:ignore[no-untyped-call] + logger.info("1") diff --git a/tests/fixtures/test_robot/test_listener_calls_log_file/Listener.py b/tests/fixtures/test_robot/test_listener_calls_log_file/Listener.py index 4cc3bbe5..44813e2c 100644 --- a/tests/fixtures/test_robot/test_listener_calls_log_file/Listener.py +++ b/tests/fixtures/test_robot/test_listener_calls_log_file/Listener.py @@ -13,4 +13,4 @@ class Listener(ListenerV3): def log_file(self, path: Path): # TODO: this doesnt log to the console so no other way to verify that it ran # https://github.com/DetachHead/pytest-robotframework/issues/39 - Path("hi").write_text("") + _ = Path("hi").write_text("") diff --git a/tests/fixtures/test_robot/test_tags_with_kwargs/conftest.py b/tests/fixtures/test_robot/test_tags_with_kwargs/conftest.py index 890adfce..477852cf 100644 --- a/tests/fixtures/test_robot/test_tags_with_kwargs/conftest.py +++ b/tests/fixtures/test_robot/test_tags_with_kwargs/conftest.py @@ -15,4 +15,4 @@ def pytest_runtest_setup(item: RobotItem): assert len(marker_names) == len(marker_kwargs) == len(markers) for marker, marker_name, marker_kwarg in zip(markers, marker_names, marker_kwargs): assert marker.name == marker_name - assert marker.kwargs == marker_kwarg # type:ignore[no-any-expr] + assert marker.kwargs == marker_kwarg diff --git a/tests/test_robot.py b/tests/test_robot.py index 4fdb8147..36493ce8 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -178,15 +178,13 @@ def pytest_collection_finish(session: Session): markers = item.own_markers result = pytester_dir.runpytest( - "--collectonly", - "--strict-markers", - plugins=[TagGetter()], # type:ignore[no-any-expr] + "--collectonly", "--strict-markers", plugins=[TagGetter()] ) result.assert_outcomes() assert markers assert len(markers) == 1 assert markers[0].name == "key" - assert markers[0].args == ("hi",) # type:ignore[no-any-expr] + assert markers[0].args == ("hi",) def test_doesnt_run_when_collecting(pytester_dir: PytesterDir): diff --git a/tests/type_tests.py b/tests/type_tests.py index 8a0143a0..1235f2d0 100644 --- a/tests/type_tests.py +++ b/tests/type_tests.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: from contextlib import ( AbstractContextManager, - _GeneratorContextManager, + _GeneratorContextManager, # pyright:ignore[reportPrivateUsage] contextmanager, ) @@ -17,19 +17,19 @@ @keyword(name="foo bar", tags=("a", "b")) def a(): ... - assert_type(a, Callable[[], None]) + _ = assert_type(a, Callable[[], None]) # keyword, no args: @keyword def g(): ... - assert_type(g, Callable[[], None]) + _ = assert_type(g, Callable[[], None]) # keyword, no args, returns Never: @keyword # make sure there's no deprecation warning here def h() -> Never: ... - assert_type(h, Callable[[], Never]) + _ = assert_type(h, Callable[[], Never]) # keyword, wrap_context_manager=True: @keyword(wrap_context_manager=True) @@ -37,7 +37,7 @@ def h() -> Never: ... def b() -> Iterator[None]: yield - assert_type(b, Callable[[], AbstractContextManager[None]]) + _ = assert_type(b, Callable[[], AbstractContextManager[None]]) # keyword, wrap_context_manager=False: @keyword(wrap_context_manager=False) @@ -45,22 +45,22 @@ def b() -> Iterator[None]: def c() -> Iterator[None]: yield - assert_type(c, Callable[[], _GeneratorContextManager[None]]) + _ = assert_type(c, Callable[[], _GeneratorContextManager[None]]) # keyword, context manager with no wrap_context_manager arg: - @keyword + @keyword # pyright:ignore[reportDeprecated] @contextmanager def d() -> Iterator[None]: yield - assert_type(d, Never) + _ = assert_type(d, Never) # keyword, non-context manager with wrap_context_manager=True: # expected type error - @keyword(wrap_context_manager=True) # type:ignore[arg-type] + @keyword(wrap_context_manager=True) # pyright:ignore[reportGeneralTypeIssues] def e(): ... # keyword, non-context manager with wrap_context_manager=False: # expected type error - @keyword(wrap_context_manager=False) # type:ignore[type-var] + @keyword(wrap_context_manager=False) # pyright:ignore[reportGeneralTypeIssues] def f(): ... diff --git a/tests/utils.py b/tests/utils.py index 9f51e32a..cb03e102 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,7 +7,7 @@ from pytest import ExitCode, Pytester if TYPE_CHECKING: - from lxml.etree import _Element + from lxml.etree import _Element # pyright:ignore[reportPrivateUsage] from typing_extensions import Never, override @@ -40,17 +40,14 @@ def output_xml(pytester: Pytester) -> _Element: return XML((pytester.path / "output.xml").read_bytes()) -def assert_robot_total_stats(pytester: Pytester, *, passed=0, skipped=0, failed=0): +def assert_robot_total_stats( + pytester: Pytester, *, passed: int = 0, skipped: int = 0, failed: int = 0 +): root = output_xml(pytester) statistics = next(child for child in root if child.tag == "statistics") total = next(child for child in statistics if child.tag == "total") result = copy(next(child for child in total if child.tag == "stat").attrib) - # https://github.com/lxml/lxml-stubs/issues/98 - assert result == { # type:ignore[comparison-overlap] - "pass": str(passed), - "fail": str(failed), - "skip": str(skipped), - } + assert result == {"pass": str(passed), "fail": str(failed), "skip": str(skipped)} def assert_log_file_exists(pytester: Pytester): @@ -61,19 +58,17 @@ def run_and_assert_assert_pytest_result( pytester: Pytester, *, pytest_args: list[str] | None = None, - subprocess=True, - passed=0, - skipped=0, - failed=0, - errors=0, - xfailed=0, + subprocess: bool = True, + passed: int = 0, + skipped: int = 0, + failed: int = 0, + errors: int = 0, + xfailed: int = 0, exit_code: ExitCode | None = None, ): - result = ( - pytester.runpytest_subprocess - if subprocess - else pytester.runpytest # type:ignore[no-any-expr] - )(*(pytest_args or [])) + result = (pytester.runpytest_subprocess if subprocess else pytester.runpytest)( + *(pytest_args or []) + ) result.assert_outcomes( passed=passed, skipped=skipped, failed=failed, errors=errors, xfailed=xfailed ) @@ -91,12 +86,12 @@ def run_and_assert_result( pytester: Pytester, *, pytest_args: list[str] | None = None, - subprocess=True, - passed=0, - skipped=0, - failed=0, - errors=0, - xfailed=0, + subprocess: bool = True, + passed: int = 0, + skipped: int = 0, + failed: int = 0, + errors: int = 0, + xfailed: int = 0, exit_code: ExitCode | None = None, ): run_and_assert_assert_pytest_result(