From d40c9483997d79ebd218140d15fb47f64599087e Mon Sep 17 00:00:00 2001 From: Kimberly Lara Date: Mon, 5 Aug 2024 11:58:38 +0200 Subject: [PATCH] unit test fix and some self review --- guibot/calibrator.py | 4 ---- guibot/config.py | 8 ++++---- guibot/controller.py | 6 +++--- guibot/finder.py | 39 +++++++++++++++++++++------------------ guibot/guibot.py | 2 +- guibot/guibot_proxy.py | 3 +-- guibot/guibot_simple.py | 32 ++++++++++++++++---------------- guibot/location.py | 4 ++-- guibot/match.py | 5 +++-- guibot/region.py | 30 +++++++++++++++--------------- guibot/target.py | 4 ++-- tests/qt5_image.py | 2 +- 12 files changed, 69 insertions(+), 70 deletions(-) diff --git a/guibot/calibrator.py b/guibot/calibrator.py index 666fc848..1baed98c 100644 --- a/guibot/calibrator.py +++ b/guibot/calibrator.py @@ -415,8 +415,6 @@ def run_performance(self, finder: Finder, **kwargs: dict[str, type]) -> float: and linear performance penalty. :param finder: finder with match configuration to use for the run - :param float max_exec_time: maximum execution time before penalizing - the run by increasing the error linearly :returns: error obtained as unity minus similarity """ self._handle_restricted_values(finder) @@ -448,8 +446,6 @@ def run_peak(self, finder: Finder, **kwargs: dict[str, type]) -> float: high similarity of one match and low similarity of all others. :param finder: finder with match configuration to use for the run - :param peak_location: (x, y) of the match whose similarity should be - maximized while all the rest minimized :returns: error obtained as unity minus similarity This run function doesn't just obtain the optimum similarity for the best diff --git a/guibot/config.py b/guibot/config.py index c470b67e..dffcc1c5 100644 --- a/guibot/config.py +++ b/guibot/config.py @@ -229,7 +229,7 @@ def preprocess_special_chars(self, value: bool = None) -> bool | None: handle them internally .. warning:: The characters will be forcefully preprocessed for the - autopy on linux (capital and special) and vncdotool (capital) backends. + autopy on linux (capital and special) and vncdotool (capital) backends. """ if value is None: return GlobalConfig._preprocess_special_chars @@ -343,7 +343,7 @@ def display_control_backend(self, value: str = None) -> str | None: their calls on the host machine. .. warning:: To use a particular backend you need to satisfy its dependencies, - i.e. the backend has to be installed or you will have unsatisfied imports. + i.e. the backend has to be installed or you will have unsatisfied imports. """ if value is None: return GlobalConfig._display_control_backend @@ -374,14 +374,14 @@ def find_backend(self, value: str = None) -> str | None: * text - text matching using EAST, ERStat, or custom text detection, followed by Tesseract or Hidden Markov Model OCR * tempfeat - a mixture of template and feature matching where the - first is used as necessary and the second as sufficient stage + first is used as necessary and the second as sufficient stage * deep - deep learning matching using convolutional neural network but customizable to any type of deep neural network * hybrid - use a composite approach with any of the above methods as matching steps in a fallback sequence .. warning:: To use a particular backend you need to satisfy its dependencies, - i.e. the backend has to be installed or you will have unsatisfied imports. + i.e. the backend has to be installed or you will have unsatisfied imports. """ if value is None: return GlobalConfig._find_backend diff --git a/guibot/controller.py b/guibot/controller.py index b5b6b3c0..2f634fa8 100644 --- a/guibot/controller.py +++ b/guibot/controller.py @@ -270,9 +270,9 @@ def mouse_scroll(self, clicks: int = 10, horizontal: bool = False) -> None: """ Scroll the mouse for a number of clicks. - :param int clicks: number of clicks to scroll up (positive) or down (negative) - :param bool horizontal: whether to perform a horizontal scroll instead - (only available on some platforms) + :param clicks: number of clicks to scroll up (positive) or down (negative) + :param horizontal: whether to perform a horizontal scroll instead + (only available on some platforms) :raises: :py:class:`NotImplementedError` if the base class method is called """ raise NotImplementedError("Method is not available for this controller implementation") diff --git a/guibot/finder.py b/guibot/finder.py index 89a0e0bf..34af29eb 100644 --- a/guibot/finder.py +++ b/guibot/finder.py @@ -63,11 +63,11 @@ def __init__(self, value: bool | int | float, :param min_val: lower boundary for the parameter range :param max_val: upper boundary for the parameter range :param delta: delta for the calibration and random value - (no calibration if `delta` < `tolerance`) + (no calibration if `delta` < `tolerance`) :param tolerance: tolerance of calibration :param fixed: whether the parameter is prevented from calibration :param enumerated: whether the parameter value belongs to an - enumeration or to a range (distance matters) + enumeration or to a range (distance matters) As a rule of thumb a good choice for the parameter delta is one fourth of the range since the delta will be used as standard deviation when @@ -339,7 +339,7 @@ def __configure_backend(self, backend: str = None, category: str = "find", log.log(9, "%s %s\n", category, self.params[category]) def configure_backend(self, backend: str = None, category: str = "find", - reset: bool = False) -> None: + reset: bool = False) -> None: """ Custom implementation of the base method. @@ -348,7 +348,7 @@ def configure_backend(self, backend: str = None, category: str = "find", self.__configure_backend(backend, category, reset) def __synchronize_backend(self, backend: str = None, category: str = "find", - reset: bool = False) -> None: + reset: bool = False) -> None: if category != "find": raise UnsupportedBackendError("Backend category '%s' is not supported" % category) if reset: @@ -1210,7 +1210,8 @@ def configure_backend(self, backend: str = None, category: str = "feature", self.__configure_backend(backend, category, reset) def __configure(self, feature_detect: str = None, feature_extract: str = None, - feature_match: str = None, reset: bool = True, **kwargs: dict[str, type]) -> None: + feature_match: str = None, reset: bool = True, + **kwargs: dict[str, type]) -> None: """ Custom implementation of the base method. @@ -1224,7 +1225,8 @@ def __configure(self, feature_detect: str = None, feature_extract: str = None, self.__configure_backend(feature_match, "fmatch") def configure(self, feature_detect: str = None, feature_extract: str = None, - feature_match: str = None, reset: bool = True, **kwargs: dict[str, type]) -> None: + feature_match: str = None, reset: bool = True, + **kwargs: dict[str, type]) -> None: """ Custom implementation of the base method. @@ -1364,14 +1366,13 @@ def find(self, needle: "Image", haystack: "Image") -> "list[Match]": self.imglog.log(40) return [] - def _project_features(self, locations_in_needle: int, ngray: int, hgray: int, similarity: float) -> list[tuple[int, int]] | None: + def _project_features(self, locations_in_needle: int, ngray: int, + hgray: int, similarity: float) -> list[tuple[int, int]] | None: """ EXTRA DOCSTRING: Feature matching backend - wrapper. Wrapper for the internal feature detection, matching and location projection used by all public feature matching functions. - - ..todo:: locations_in_needle, ngray, hgray, similarity are not documented """ # default logging in case no match is found (further overridden by match stages) self.imglog.locations.append((0, 0)) @@ -1414,7 +1415,8 @@ def _project_features(self, locations_in_needle: int, ngray: int, hgray: int, si self._log_features(30, self.imglog.locations, self.imglog.hotmaps[-1], 3, 0, 0, 255) return locations_in_haystack - def _detect_features(self, ngray: int, hgray: int, detect: str, extract: str) -> tuple[str, str, str, str]: + def _detect_features(self, ngray: int, hgray: int, detect: str, + extract: str) -> tuple[str, str, str, str]: """ EXTRA DOCSTRING: Feature matching backend - detection/extraction stage (1). @@ -1466,7 +1468,8 @@ def _detect_features(self, ngray: int, hgray: int, detect: str, extract: str) -> return (nkeypoints, ndescriptors, hkeypoints, hdescriptors) def _match_features(self, nkeypoints: str, ndescriptors: str, - hkeypoints: str, hdescriptors: str, match: str) -> tuple[list[str], list[str]]: + hkeypoints: str, hdescriptors: str, + match: str) -> tuple[list[str], list[str]]: """ EXTRA DOCSTRING: Feature matching backend - matching stage (2). @@ -1571,7 +1574,8 @@ def symmetry_test(nmatches: str, hmatches: str) -> list[str]: return (match_nkeypoints, match_hkeypoints) - def _project_locations(self, locations_in_needle: int, mnkp: str, mhkp: str) -> list[tuple[int, int]]: + def _project_locations(self, locations_in_needle: int, mnkp: str, + mhkp: str) -> list[tuple[int, int]]: """ EXTRA DOCSTRING: Feature matching backend - projecting stage (3). @@ -1590,8 +1594,6 @@ def _project_locations(self, locations_in_needle: int, mnkp: str, mhkp: str) -> i.e. the upper left corner of the image. In case of wild transformations of the needle in the haystack this has to be reconsidered and the needle center becomes obligatory. - - ..todo:: locations_in_needle, mnkp, mhkp are not documented """ # check matches consistency assert len(mnkp) == len(mhkp) @@ -1679,9 +1681,9 @@ def log(self, lvl: int) -> None: self.imglog.clear() ImageLogger.step += 1 - def _log_features(self, lvl: int, locations: Location, hotmap: "Image", radius: int = 0, r: int = 255, - g: int = 255, b: int = 255) -> None: - # ..todo:: locations, hotmap are not documented + def _log_features(self, lvl: int, locations: Location, hotmap: "Image", + radius: int = 0, r: int = 255, g: int = 255, + b: int = 255) -> None: if lvl < self.imglog.logging_level: return import cv2 @@ -1990,7 +1992,8 @@ def __configure(self, text_detector: str = None, text_recognizer: str = None, def configure(self, text_detector: str = None, text_recognizer: str = None, threshold_filter: str = None, threshold_filter2: str = None, - threshold_filter3: str = None, reset: bool = True, **kwargs: dict[str, type]) -> None: + threshold_filter3: str = None, reset: bool = True, + **kwargs: dict[str, type]) -> None: """ Custom implementation of the base method. diff --git a/guibot/guibot.py b/guibot/guibot.py index 61bd08f7..c39ce195 100644 --- a/guibot/guibot.py +++ b/guibot/guibot.py @@ -65,7 +65,7 @@ def add_path(self, directory: str) -> None: Add a path to the list of currently accessible paths if it wasn't already added. - :param str directory: path to add + :param directory: path to add """ self.file_resolver.add_path(directory) diff --git a/guibot/guibot_proxy.py b/guibot/guibot_proxy.py index 4e42d9cc..ac450fca 100644 --- a/guibot/guibot_proxy.py +++ b/guibot/guibot_proxy.py @@ -42,7 +42,7 @@ from .guibot import GuiBot -def serialize_custom_error(class_obj: "classobj") -> dict[str, str | getset_descriptor | dictproxy]: +def serialize_custom_error(class_obj: "classobj") -> dict[str, str | "getset_descriptor" | "dictproxy"]: """ Serialization method for the :py:class:`errors.UnsupportedBackendError` which was chosen just as a sample. @@ -90,7 +90,6 @@ def __init__(self, dc: Controller = None, cv: Finder = None) -> None: register_exception_serialization() def _proxify(self, obj: str) -> str: - #..todo:: obj is not documented if isinstance(obj, (int, float, bool, str)) or obj is None: return obj if obj not in self._pyroDaemon.objectsById.values(): diff --git a/guibot/guibot_simple.py b/guibot/guibot_simple.py index 1ff750cc..c1d73297 100644 --- a/guibot/guibot_simple.py +++ b/guibot/guibot_simple.py @@ -91,7 +91,7 @@ def sample(*args: tuple[type, ...], **kwargs: dict[str, type]) -> float: return guibot.sample(*args, **kwargs) -def exists(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def exists(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.exists(*args, **kwargs) @@ -109,7 +109,7 @@ def wait_vanish(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Region: return guibot.wait_vanish(*args, **kwargs) -def get_mouse_location(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Location: +def get_mouse_location(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Location": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.get_mouse_location(*args, **kwargs) @@ -121,79 +121,79 @@ def idle(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Region: return guibot.idle(*args, **kwargs) -def hover(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def hover(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.hover(*args, **kwargs) -def click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.click(*args, **kwargs) -def right_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def right_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.right_click(*args, **kwargs) -def middle_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def middle_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.middle_click(*args, **kwargs) -def double_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def double_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.double_click(*args, **kwargs) -def multi_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def multi_click(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.multi_click(*args, **kwargs) -def click_expect(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def click_expect(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.click_expect(*args, **kwargs) -def click_vanish(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def click_vanish(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.click_vanish(*args, **kwargs) -def click_at_index(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def click_at_index(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.click_at_index(*args, **kwargs) -def mouse_down(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def mouse_down(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.mouse_down(*args, **kwargs) -def mouse_up(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def mouse_up(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.mouse_up(*args, **kwargs) -def mouse_scroll(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def mouse_scroll(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.mouse_scroll(*args, **kwargs) -def drag_drop(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def drag_drop(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.drag_drop(*args, **kwargs) @@ -205,7 +205,7 @@ def drag_from(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Region: return guibot.drag_from(*args, **kwargs) -def drop_at(*args: tuple[type, ...], **kwargs: dict[str, type]) -> Match: +def drop_at(*args: tuple[type, ...], **kwargs: dict[str, type]) -> "Match": """See :py:class:`guibot.guibot.GuiBot` and its inherited :py:class:`guibot.region.Region` for details.""" check_initialized() return guibot.drop_at(*args, **kwargs) diff --git a/guibot/location.py b/guibot/location.py index 35f27fcc..d6668e4b 100644 --- a/guibot/location.py +++ b/guibot/location.py @@ -36,8 +36,8 @@ def __init__(self, xpos: int = 0, ypos: int = 0) -> None: """ Build a location object. - :param int xpos: x coordinate of the location - :param int ypos: y coordinate of the location + :param xpos: x coordinate of the location + :param ypos: y coordinate of the location """ self._xpos = xpos self._ypos = ypos diff --git a/guibot/match.py b/guibot/match.py index 6158072e..26fc930f 100644 --- a/guibot/match.py +++ b/guibot/match.py @@ -37,8 +37,9 @@ class Match(Region): of matches on a screen. """ - def __init__(self, xpos: int, ypos: int, width: int, height: int, dx: int = 0, dy: int = 0, - similarity: float = 0.0, dc: Controller = None, cv: "Finder" = None) -> None: + def __init__(self, xpos: int, ypos: int, width: int, height: int, + dx: int = 0, dy: int = 0, similarity: float = 0.0, + dc: Controller = None, cv: "Finder" = None) -> None: """ Build a match object. diff --git a/guibot/region.py b/guibot/region.py index 40049ddf..ec7c7b8c 100644 --- a/guibot/region.py +++ b/guibot/region.py @@ -646,7 +646,7 @@ def right_click(self, target_or_location: "Match | Location | str | Target", return match def middle_click(self, target_or_location: "Match | Location | str | Target", - modifiers: list[str] = None) -> "Match | None": + modifiers: list[str] = None) -> "Match | None": """ Click on a target or location using the middle mouse button and optionally holding special keys. @@ -661,7 +661,7 @@ def middle_click(self, target_or_location: "Match | Location | str | Target", return match def double_click(self, target_or_location: "Match | Location | str | Target", - modifiers: list[str] = None) -> "Match | None": + modifiers: list[str] = None) -> "Match | None": """ Double click on a target or location using the left mouse button and optionally holding special keys. @@ -746,10 +746,10 @@ def click_at_index(self, anchor: str | Target, index: int = 0, :param anchor: image to find all matches of :param index: index of the match to click on (assuming >=1 matches), - sorted according to their (x,y) coordinates + sorted according to their (x,y) coordinates :param find_number: expected number of matches which is necessary - for fast failure in case some elements are not visualized and/or - proper matching result + for fast failure in case some elements are not visualized and/or + proper matching result :param timeout: timeout before which the number of matches should be found :returns: match from finding the target of the desired index @@ -805,7 +805,7 @@ def mouse_down(self, target_or_location: "Match | Location | str | Target", return match def mouse_up(self, target_or_location: "Match | Location | str | Target", - button: int = None) -> "Match | None": + button: int = None) -> "Match | None": """ Release an arbitrary mouse button on a target or location. @@ -927,8 +927,6 @@ def press_at(self, keys: str | list[str], This method is similar to :py:func:`Region.press_keys` but with an extra argument like :py:func:`Region.click`. - - ..todo:: keys, target_or_location are not documented """ keys_list = self._parse_keys(keys, target_or_location) match = self.click(target_or_location) @@ -1087,8 +1085,8 @@ def _parse_text(self, text: list[str] | str, return text_list """Mixed (form) methods""" - def click_at(self, anchor: "Match | Location | Target | str", dx: int, dy: int, - count: int = 1) -> "Region": + def click_at(self, anchor: "Match | Location | Target | str", + dx: int, dy: int, count: int = 1) -> "Region": """ Clicks on a relative location using a displacement from an anchor. @@ -1112,8 +1110,9 @@ def click_at(self, anchor: "Match | Location | Target | str", dx: int, dy: int, return self - def fill_at(self, anchor: "Match | Location | Target | str", text: str, dx: int, dy: int, - del_flag: bool = True, esc_flag: bool = True, mark_clicks: int = 1) -> "Region": + def fill_at(self, anchor: "Match | Location | Target | str", + text: str, dx: int, dy: int, del_flag: bool = True, + esc_flag: bool = True, mark_clicks: int = 1) -> "Region": """ Fills a new text at a text box using a displacement from an anchor. @@ -1160,9 +1159,10 @@ def fill_at(self, anchor: "Match | Location | Target | str", text: str, dx: int, return self - def select_at(self, anchor: "Match | Location | Target | str", image_or_index: str | int, - dx: int, dy: int, dw: int = 0, dh: int = 0, ret_flag: bool = True, - mark_clicks: int = 1, tries: int = 3) -> "Region": + def select_at(self, anchor: "Match | Location | Target | str", + image_or_index: str | int, dx: int, dy: int, dw: int = 0, + dh: int = 0, ret_flag: bool = True, mark_clicks: int = 1, + tries: int = 3) -> "Region": """ Select an option at a dropdown list using either an integer index or an option image if the order cannot be easily inferred. diff --git a/guibot/target.py b/guibot/target.py index 38551093..bb273dc1 100644 --- a/guibot/target.py +++ b/guibot/target.py @@ -361,7 +361,7 @@ def __init__(self, value: str = None, text_filename: str = None, :param match_settings: predefined configuration for the CV backend if any """ super(Text, self).__init__(match_settings) - self.value = value + self.value: str = value self.filename = text_filename try: @@ -374,7 +374,7 @@ def __init__(self, value: str = None, text_filename: str = None, def __str__(self) -> str | None: """Provide a part of the text value.""" - return self.str(value[:30]).replace('/', '').replace('\\', '') + return self.value[:30].replace('/', '').replace('\\', '') def load(self, filename: str, **kwargs: dict[str, type]) -> None: """ diff --git a/tests/qt5_image.py b/tests/qt5_image.py index 2e0d6fce..ea50852a 100644 --- a/tests/qt5_image.py +++ b/tests/qt5_image.py @@ -23,7 +23,7 @@ class ImageWithLayout(QtWidgets.QWidget): - def __init__(self, filename: str, title: str = "show_picture", parent=None) -> None: + def __init__(self, filename: str, title: str = "show_picture", parent = None) -> None: QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle(title)