From 1378c84a85db05c0c5acce4d95cec8da00f6fc3a Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 3 Sep 2024 19:10:50 +0100 Subject: [PATCH] typing --- pyproject.toml | 16 ++++++ sikuli/run.py | 2 +- sikuli/script/app.py | 10 +++- sikuli/script/finder.py | 6 +- sikuli/script/image.py | 2 +- sikuli/script/location.py | 8 +-- sikuli/script/match.py | 3 +- sikuli/script/rectangle.py | 27 ++++----- sikuli/script/region.py | 114 ++++++++++++++++++++----------------- sikuli/script/robot.py | 12 ++-- sikuli/script/screen.py | 5 +- sikuli/script/settings.py | 4 +- 12 files changed, 120 insertions(+), 89 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e1cdbb5..3d2e2a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,22 @@ source = ["sikuli"] [tool.mypy] files = "sikuli" +[[tool.mypy.overrides]] +module = "autopy.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "pyperclip.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "pytesseract.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "pudb.*" +ignore_missing_imports = true + [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" diff --git a/sikuli/run.py b/sikuli/run.py index f3c51d1..f131830 100644 --- a/sikuli/run.py +++ b/sikuli/run.py @@ -25,7 +25,7 @@ def run(folder: str) -> None: sys.path.append(os.path.dirname(folder)) # FIXME: adding parent is unofficial Settings.ImagePaths.append(folder) try: - runpy._run_module_as_main(module) + runpy._run_module_as_main(module) # type: ignore # mod = __import__(module) except KeyboardInterrupt: pass diff --git a/sikuli/script/app.py b/sikuli/script/app.py index 156858b..a48ac59 100644 --- a/sikuli/script/app.py +++ b/sikuli/script/app.py @@ -1,18 +1,22 @@ +import typing as t + from .region import Region from .robot import Robot class App(object): @staticmethod - def open(application: str = None) -> "App": + def open(application: t.Optional[str] = None) -> "App": raise NotImplementedError("App.open(%r) not implemented" % application) # FIXME @staticmethod - def focus(application: str = None) -> "App": + def focus(application: t.Optional[str] = None) -> "App": + assert application is not None Robot.focus(application) + return App() @staticmethod - def close(application: str = None) -> None: + def close(application: t.Optional[str] = None) -> None: raise NotImplementedError( "App.close(%r) not implemented" % application ) # FIXME diff --git a/sikuli/script/finder.py b/sikuli/script/finder.py index 2914b3b..7784088 100644 --- a/sikuli/script/finder.py +++ b/sikuli/script/finder.py @@ -10,10 +10,10 @@ def __init__(self, filename: str): self.filename = filename def find(self, filename: str, similarity: float = 0.7): - pass + raise NotImplementedError("Finder.find(%r, %r) not implemented" % (filename, similarity)) # FIXME def hasNext(self) -> bool: - pass + raise NotImplementedError("Finder.hasNext() not implemented") # FIXME def next(self) -> Match: - pass + raise NotImplementedError("Finder.next() not implemented") # FIXME diff --git a/sikuli/script/image.py b/sikuli/script/image.py index e3f2b8d..1b4783f 100644 --- a/sikuli/script/image.py +++ b/sikuli/script/image.py @@ -35,7 +35,7 @@ def __init__(self, base: Union[PILImage.Image, str]) -> None: if not full_path: raise Exception("Couldn't find %r in %r" % (base, Settings.ImagePaths)) - i = PILImage.open(full_path) + i: PILImage.Image = PILImage.open(full_path) if Settings.Scale != 1.0: # resize to multiple of 1/scale # eg scale=0.5, round to a multiple of 2 diff --git a/sikuli/script/location.py b/sikuli/script/location.py index 4e4e74b..b675bca 100644 --- a/sikuli/script/location.py +++ b/sikuli/script/location.py @@ -2,7 +2,7 @@ http://doc.sikuli.org/location.html """ -from typing import Tuple +import typing as t from .sikulpy import unofficial @@ -15,8 +15,8 @@ def __init__(self, x: float, y: float) -> None: def __repr__(self) -> str: return "Location(%r, %r)" % (self.x, self.y) - def __eq__(self, other: "Location") -> bool: - return self.x == other.x and self.y == other.y + def __eq__(self, other: object) -> bool: + return isinstance(other, Location) and self.x == other.x and self.y == other.y def __add__(self, other: "Location") -> "Location": return Location(self.x + other.x, self.y + other.y) @@ -30,7 +30,7 @@ def __mul__(self, factor: float) -> "Location": return Location(self.x * factor, self.y * factor) @unofficial - def getXY(self) -> Tuple[float, float]: + def getXY(self) -> t.Tuple[float, float]: return self.getX(), self.getY() def getX(self) -> float: diff --git a/sikuli/script/match.py b/sikuli/script/match.py index acc0f27..1d0caf2 100644 --- a/sikuli/script/match.py +++ b/sikuli/script/match.py @@ -1,6 +1,7 @@ """ http://doc.sikuli.org/match.html """ +import typing as t from .region import Region from .rectangle import Rectangle @@ -10,7 +11,7 @@ class Match(Region): def __init__(self, rect: Rectangle, sim: float, targetOffset: Location): Region.__init__(self, rect) - self._name = None + self._name: t.Optional[str] = None self._score = sim self._targetOffset = targetOffset diff --git a/sikuli/script/rectangle.py b/sikuli/script/rectangle.py index 5fa9ca9..0bc97da 100644 --- a/sikuli/script/rectangle.py +++ b/sikuli/script/rectangle.py @@ -7,7 +7,7 @@ class Rectangle(object): - def __init__(self, x: int = 0, y: int = 0, w: int = 0, h: int = 0) -> None: + def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0) -> None: self.x = x self.y = y self.w = w @@ -22,24 +22,24 @@ def __repr__(self) -> str: self.h, ) - def __eq__(self, b: "Rectangle") -> bool: - return self.x == b.x and self.y == b.y and self.w == b.w and self.h == b.h + def __eq__(self, b: object) -> bool: + return isinstance(b, Rectangle) and self.x == b.x and self.y == b.y and self.w == b.w and self.h == b.h - def __ne__(self, b: "Rectangle") -> bool: + def __ne__(self, b: object) -> bool: return not self.__eq__(b) # set - def setX(self, x: int) -> None: + def setX(self, x: float) -> None: self.x = x - def setY(self, y: int) -> None: + def setY(self, y: float) -> None: self.y = y - def setW(self, w: int) -> None: + def setW(self, w: float) -> None: self.w = w - def setH(self, h: int) -> None: + def setH(self, h: float) -> None: self.h = h def moveTo(self, location: Location) -> "Rectangle": @@ -60,16 +60,16 @@ def morphTo(self, reg: "Region") -> "Rectangle": # get - def getX(self) -> int: + def getX(self) -> float: return self.x - def getY(self) -> int: + def getY(self) -> float: return self.y - def getW(self) -> int: + def getW(self) -> float: return self.w - def getH(self) -> int: + def getH(self) -> float: return self.h def getRect(self) -> "Rectangle": @@ -78,7 +78,8 @@ def getRect(self) -> "Rectangle": def getCenter(self) -> Location: return Location(self.x + self.w // 2, self.y + self.h // 2) - getTarget = getCenter + def getTarget(self) -> Location: + return self.getCenter() def getTopLeft(self) -> Location: return Location(self.x, self.y) diff --git a/sikuli/script/region.py b/sikuli/script/region.py index 323ed28..7497fa1 100644 --- a/sikuli/script/region.py +++ b/sikuli/script/region.py @@ -21,9 +21,10 @@ from .exc import FindFailed -from typing import Union, List, Tuple, Any, Optional, TYPE_CHECKING +from typing import List, Tuple +import typing as t -if TYPE_CHECKING: +if t.TYPE_CHECKING: from .screen import Screen from .match import Match @@ -50,7 +51,7 @@ def __init__(self, rect, b=None, c=None, d=None): @unofficial def freeze(self) -> None: - self._frozen = Robot.capture((self.x, self.y, self.w, self.h)) + self._frozen = Robot.capture((int(self.x), int(self.y), int(self.w), int(self.h))) @unofficial def thaw(self) -> None: @@ -80,10 +81,10 @@ def _copy(self) -> "Region": r._screen = self._screen return r - def offset(self, l: Location) -> "Region": + def offset(self, location: Location) -> "Region": r = self._copy() - r.x += l.x - r.y += l.y + r.x += location.x + r.y += location.y return r def inside(self) -> "Region": @@ -97,32 +98,32 @@ def nearby(self, range_: int = 50) -> "Region": r.h += range_ * 2 return r - def above(self, range_: int = None) -> "Region": - if not range_: + def above(self, range_: t.Optional[int] = None) -> "Region": + if range_ is None: range_ = self.y - self._screen.y r = self._copy() r.h = range_ r.y -= range_ return r - def below(self, range_: int = None) -> "Region": - if not range_: + def below(self, range_: t.Optional[int] = None) -> "Region": + if range_ is None: range_ = self._screen.h - (self.y + self.h) r = self._copy() r.y += r.h r.h = range_ return r - def left(self, range_: int = None) -> "Region": - if not range_: + def left(self, range_: t.Optional[int] = None) -> "Region": + if range_ is None: range_ = self.x - self._screen.x r = self._copy() r.w = range_ r.x -= range_ return r - def right(self, range_: int = None) -> "Region": - if not range_: + def right(self, range_: t.Optional[int] = None) -> "Region": + if range_ is None: range_ = self._screen.w - (self.x + self.w) r = self._copy() r.x += r.w @@ -131,14 +132,14 @@ def right(self, range_: int = None) -> "Region": # finding - def find(self, target: Union[Pattern, str]) -> "Match": + def find(self, target: t.Union[Pattern, str]) -> "Match": return self.findAll(target)[0] - def findAll(self, target: Union[Pattern, str]) -> List["Match"]: + def findAll(self, target: t.Union[Pattern, str]) -> List["Match"]: if not isinstance(target, Pattern): target = Pattern(target) - region = self._frozen or Robot.capture((self.x, self.y, self.w, self.h)) + region = self._frozen or Robot.capture((int(self.x), int(self.y), int(self.w), int(self.h))) matches = [] from .match import Match @@ -199,7 +200,7 @@ def findAll(self, target: Union[Pattern, str]) -> List["Match"]: self._last_matches = matches return matches - def wait(self, target: Union[Pattern, str], seconds: float = None) -> "Match": + def wait(self, target: t.Union[Pattern, str], seconds: t.Optional[float] = None) -> "Match": until = time() + (seconds or self.autoWaitTimeout) while True: x = self.find(target) @@ -211,7 +212,7 @@ def wait(self, target: Union[Pattern, str], seconds: float = None) -> "Match": raise FindFailed() - def waitVanish(self, target: Union[Pattern, str], seconds: float = None) -> bool: + def waitVanish(self, target: t.Union[Pattern, str], seconds: t.Optional[float] = None) -> bool: until = time() + (seconds or self.autoWaitTimeout) while True: if not self.find(target): @@ -222,8 +223,8 @@ def waitVanish(self, target: Union[Pattern, str], seconds: float = None) -> bool return False def exists( - self, target: Union[Pattern, str], seconds: float = None - ) -> Optional["Match"]: + self, target: t.Union[Pattern, str], seconds: t.Optional[float] = None + ) -> t.Optional["Match"]: try: return self.wait(target, seconds) except FindFailed: @@ -231,17 +232,17 @@ def exists( # observing - def onAppear(self, target: Union[Pattern, str], handler): + def onAppear(self, target: t.Union[Pattern, str], handler): raise NotImplementedError( "Region.onAppear(%r, %r) not implemented" % (target, handler) ) # FIXME - def onVanish(self, target: Union[Pattern, str], handler): + def onVanish(self, target: t.Union[Pattern, str], handler): raise NotImplementedError( "Region.onVanish(%r, %r) not implemented" % (target, handler) ) # FIXME - def onChange(self, target: Union[Pattern, str], handler): + def onChange(self, target: t.Union[Pattern, str], handler): raise NotImplementedError( "Region.onChange(%r, %r) not implemented" % (target, handler) ) # FIXME @@ -256,12 +257,12 @@ def stopObserver(self) -> None: # actions - def _targetOrLast(self, target: Union[Pattern, str]) -> Union[Pattern, str]: - if not target: - target = self.getLastMatch() + def _targetOrLast(self, target: t.Optional[t.Union[Pattern, str]]) -> t.Union[Pattern, str, Match]: + if target is None: + return self.getLastMatch() return target - def _toLocation(self, target: Union[Pattern, str]) -> Location: + def _toLocation(self, target: t.Union[Pattern, str, Rectangle, Location]) -> Location: if isinstance(target, str): target = Pattern(target) if isinstance(target, Pattern): @@ -270,6 +271,7 @@ def _toLocation(self, target: Union[Pattern, str]) -> Location: target = target.getTarget() if isinstance(target, Location): return target + raise ValueError("Invalid target %r" % target) # mouse @@ -280,7 +282,7 @@ def mouseUp(self, button): Robot.mouseUp(button) def mouseMove( - self, target: Union[Pattern, str], _delay: float = None + self, target: t.Optional[t.Union[Pattern, str]], _delay: t.Optional[float] = None ) -> Tuple[float, float]: if _delay is None: _delay = Settings.MoveMouseDelay @@ -300,7 +302,7 @@ def mouseMove( sleep(0.5) return pt - def wheel(self, target: Union[Pattern, str], button, steps=1): + def wheel(self, target: t.Optional[t.Union[Pattern, str]], button, steps=1): self.mouseMove(target) for _ in range(0, steps): self.mouseDown(button) @@ -308,7 +310,7 @@ def wheel(self, target: Union[Pattern, str], button, steps=1): self.mouseUp(button) sleep(0.1) - def click(self, target: Union[Pattern, str] = None, modifiers: int = None) -> int: + def click(self, target: t.Optional[t.Union[Pattern, str]] = None, modifiers: t.Optional[int] = None) -> int: # FIXME: modifiers self.mouseMove(target) self.mouseDown(Mouse.LEFT) @@ -317,7 +319,7 @@ def click(self, target: Union[Pattern, str] = None, modifiers: int = None) -> in return 1 # no. of clicks def doubleClick( - self, target: Union[Pattern, str] = None, modifiers: int = None + self, target: t.Optional[t.Union[Pattern, str]] = None, modifiers: t.Optional[int] = None ) -> int: # FIXME: modifiers self.mouseMove(target) @@ -331,7 +333,7 @@ def doubleClick( return 1 # no. of double clicks def rightClick( - self, target: Union[Pattern, str] = None, modifiers: int = None + self, target: t.Optional[t.Union[Pattern, str]] = None, modifiers: t.Optional[int] = None ) -> int: # FIXME: modifiers self.mouseMove(target) @@ -340,31 +342,31 @@ def rightClick( self.mouseUp(Mouse.RIGHT) return 1 # no. of clicks - def highlight(self, seconds: float = None) -> None: + def highlight(self, seconds: t.Optional[float] = None) -> None: # FIXME: display rectangle HUD pass - def hover(self, target: Union[Pattern, str] = None) -> None: + def hover(self, target: t.Optional[t.Union[Pattern, str]] = None) -> None: self.mouseMove(target) def dragDrop( self, - target1: Union[Pattern, str], - target2: Union[Pattern, str], - modifiers: int = None, + target1: t.Union[Pattern, str], + target2: t.Union[Pattern, str], + modifiers: t.Optional[int] = None, ) -> None: self.drag(target1) if Settings.DelayBeforeDrag: sleep(Settings.DelayBeforeDrag) self.dropAt(target2) - def drag(self, target: Union[Pattern, str] = None) -> None: + def drag(self, target: t.Optional[t.Union[Pattern, str]] = None) -> None: self.mouseMove(target) if Settings.DelayBeforeMouseDown: sleep(Settings.DelayBeforeMouseDown) self.mouseDown(Mouse.LEFT) - def dropAt(self, target: Union[Pattern, str] = None, delay: float = None) -> None: + def dropAt(self, target: t.Optional[t.Union[Pattern, str]] = None, delay: t.Optional[float] = None) -> None: self.mouseMove(target) if delay is not None: sleep(delay) @@ -382,20 +384,26 @@ def keyDown(self, key): def type( self, - target: Union[Pattern, str] = None, - text: str = None, - modifiers: int = None, + a: t.Optional[t.Union[Pattern, str]] = None, + b: t.Optional[str] = None, + modifiers: t.Optional[int] = None, ) -> None: - if text is None: - text = target + target: t.Optional[t.Union[Pattern, str]] = None + text: t.Optional[str] = None + + if a is not None and b is not None: + target = a + text = b + if b is None and isinstance(a, str): + text = a target = None - if target: + if target is not None: self.click(target) Robot.type(text, modifiers) - def paste(self, target: Union[Pattern, str] = None, text: str = None) -> None: + def paste(self, target: t.Optional[t.Union[Pattern, str]] = None, text: t.Optional[str] = None) -> None: """ Paste the text at a click point. @@ -418,7 +426,7 @@ def text(self) -> str: try: import pytesseract # EXT - pil = Robot.capture((self.x, self.y, self.w, self.h)).img + pil = Robot.capture((int(self.x), int(self.y), int(self.w), int(self.h))).img cvimg = cv2.cvtColor(np.array(pil.convert("RGB")), cv2.COLOR_RGB2BGR) _, cvimg = cv2.threshold(cvimg, 127, 255, cv2.THRESH_BINARY) # cvimg = cv.adaptiveThreshold( @@ -451,12 +459,12 @@ def getThrowException(self) -> bool: # special - def getRegionFromPSRM(self, target: Union[Pattern, str]) -> "Region": + def getRegionFromPSRM(self, target: t.Union[Pattern, str]) -> "Region": raise NotImplementedError( "Region.getRegionFromPSRM(%r) not implemented" % target ) # FIXME - def getLocationFromPSRML(self, target: Union[Pattern, str]) -> Location: + def getLocationFromPSRML(self, target: t.Union[Pattern, str]) -> Location: raise NotImplementedError( "Region.getLocationFromPSRML(%r) not implemented" % target ) # FIXME @@ -469,6 +477,6 @@ class Type(Enum): CHANGE = 2 type = Type.APPEAR - pattern = None # type: Any - match = None # type: Match - changes = None # type: List[Match] + pattern: t.Optional[t.Any] = None + match: t.Optional[Match] = None + changes: t.Optional[t.List[Match]] = None diff --git a/sikuli/script/robot.py b/sikuli/script/robot.py index 5ad9207..27d06ca 100644 --- a/sikuli/script/robot.py +++ b/sikuli/script/robot.py @@ -1,9 +1,8 @@ -import warnings import platform import subprocess from enum import Enum from time import time -from typing import Tuple +import typing as t from PIL import Image as PILImage # EXT @@ -38,7 +37,7 @@ class Robot(object): } @staticmethod - def mouseMove(xy: Tuple[int, int]): + def mouseMove(xy: t.Tuple[int, int]): log.info("mouseMove(%r)", xy) x, y = int(xy[0]), int(xy[1]) @@ -55,7 +54,7 @@ def mouseUp(button): autopy.mouse.toggle(Robot.autopyMouseMap[button], False) @staticmethod - def getMouseLocation() -> Tuple[int, int]: + def getMouseLocation() -> t.Tuple[int, int]: xy = autopy.mouse.location() return int(xy[0]), int(xy[1]) @@ -103,15 +102,16 @@ def getNumberScreens() -> int: return len(sct.monitors) - 1 @staticmethod - def screenSize() -> Tuple[int, int, int, int]: + def screenSize() -> t.Tuple[int, int, int, int]: with mss.mss() as sct: return 0, 0, sct.monitors[0]["width"], sct.monitors[0]["height"] @staticmethod - def capture(bbox: Tuple[int, int, int, int] = None) -> Image: + def capture(bbox: t.Optional[t.Tuple[int, int, int, int]] = None) -> Image: _start = time() with mss.mss() as sct: + assert bbox is not None sct_img = sct.grab( {"left": bbox[0], "top": bbox[1], "width": bbox[2], "height": bbox[3]} ) diff --git a/sikuli/script/screen.py b/sikuli/script/screen.py index 0131986..cb82006 100644 --- a/sikuli/script/screen.py +++ b/sikuli/script/screen.py @@ -3,6 +3,7 @@ """ import tempfile +import typing as t from .region import Region from .rectangle import Rectangle @@ -26,11 +27,11 @@ def capture(self, rect: Rectangle) -> str: if not rect: rect = self.getBounds() fn = tempfile.mktemp(".png") - img = Robot.capture((rect.x, rect.y, rect.w, rect.h)) + img = Robot.capture((int(rect.x), int(rect.y), int(rect.w), int(rect.h))) img.save(fn) return fn - def selectRegion(self, text: str = None) -> Region: + def selectRegion(self, text: t.Optional[str] = None) -> Region: # interactive selection, with label raise NotImplementedError( "Screen.selectRegion(%r) not implemented" % text diff --git a/sikuli/script/settings.py b/sikuli/script/settings.py index 50ad718..6dac264 100644 --- a/sikuli/script/settings.py +++ b/sikuli/script/settings.py @@ -1,4 +1,4 @@ -from typing import List +import typing as t class Settings(object): @@ -7,7 +7,7 @@ class Settings(object): Scale = 1 # FIXME: unofficial Channel = None # FIXME: unofficial - ImagePaths = [] # type: List[str] + ImagePaths: t.List[str] = [] # Either option might be switched on (True) or off (False), to show or # hide the respective message type in the IDE console or on command line