uiautodev 0.3.7__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {uiautodev-0.3.7 → uiautodev-0.5.0}/PKG-INFO +5 -5
  2. {uiautodev-0.3.7 → uiautodev-0.5.0}/README.md +2 -0
  3. {uiautodev-0.3.7 → uiautodev-0.5.0}/pyproject.toml +5 -6
  4. uiautodev-0.5.0/uiautodev/__init__.py +8 -0
  5. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/cli.py +1 -1
  6. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/command_proxy.py +44 -8
  7. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/command_types.py +8 -0
  8. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/android.py +40 -50
  9. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/base_driver.py +33 -4
  10. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/ios.py +20 -14
  11. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/model.py +5 -1
  12. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/router/device.py +19 -7
  13. uiautodev-0.3.7/uiautodev/__init__.py +0 -12
  14. {uiautodev-0.3.7 → uiautodev-0.5.0}/LICENSE +0 -0
  15. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/__main__.py +0 -0
  16. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/app.py +0 -0
  17. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/appium_proxy.py +0 -0
  18. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/case.py +0 -0
  19. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/common.py +0 -0
  20. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/appium.py +0 -0
  21. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/mock.py +0 -0
  22. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
  23. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/driver/udt/udt.py +0 -0
  24. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/exceptions.py +0 -0
  25. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/provider.py +0 -0
  26. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/router/xml.py +0 -0
  27. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/static/demo.html +0 -0
  28. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/utils/common.py +0 -0
  29. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/utils/exceptions.py +0 -0
  30. {uiautodev-0.3.7 → uiautodev-0.5.0}/uiautodev/utils/usbmux.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiautodev
3
- Version: 0.3.7
3
+ Version: 0.5.0
4
4
  Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
5
5
  Home-page: https://uiauto.dev
6
6
  License: MIT
@@ -14,13 +14,10 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
- Provides-Extra: appium
18
- Requires-Dist: adbutils (>=2.6.0,<3.0.0)
19
- Requires-Dist: appium-python-client (>=4.0.0,<5.0.0) ; extra == "appium"
17
+ Requires-Dist: adbutils (>=2.7.0,<3.0.0)
20
18
  Requires-Dist: click (>=8.1.7,<9.0.0)
21
19
  Requires-Dist: construct
22
20
  Requires-Dist: fastapi (>=0.111.0,<0.112.0)
23
- Requires-Dist: httpretty (>=1.1.4,<2.0.0)
24
21
  Requires-Dist: httpx
25
22
  Requires-Dist: lxml
26
23
  Requires-Dist: pillow
@@ -29,6 +26,7 @@ Requires-Dist: pydantic (>=2.6,<3.0)
29
26
  Requires-Dist: pygments (>=2)
30
27
  Requires-Dist: uiautomator2 (>=2)
31
28
  Requires-Dist: uvicorn[standard]
29
+ Requires-Dist: wdapy (>=0.2.2,<0.3.0)
32
30
  Description-Content-Type: text/markdown
33
31
 
34
32
  # uiautodev
@@ -37,6 +35,8 @@ Description-Content-Type: text/markdown
37
35
 
38
36
  https://uiauto.dev
39
37
 
38
+ > backup site: https://uiauto.devsleep.com
39
+
40
40
  UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
41
41
 
42
42
  # Install
@@ -4,6 +4,8 @@
4
4
 
5
5
  https://uiauto.dev
6
6
 
7
+ > backup site: https://uiauto.devsleep.com
8
+
7
9
  UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
8
10
 
9
11
  # Install
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiautodev"
3
- version = "0.3.7"
3
+ version = "0.5.0"
4
4
  description = "Mobile UI Automation, include UI hierarchy inspector, script recorder"
5
5
  homepage = "https://uiauto.dev"
6
6
  authors = ["codeskyblue <codeskyblue@gmail.com>"]
@@ -10,22 +10,21 @@ readme = "README.md"
10
10
  [tool.poetry.dependencies]
11
11
  python = "^3.8"
12
12
  pillow = "*"
13
- adbutils = "^2.6.0"
13
+ adbutils = "^2.7.0"
14
14
  construct = "*"
15
15
  lxml = "*"
16
16
  click = "^8.1.7"
17
17
  pygments = ">=2"
18
- httpretty = {version = "^1.1.4", optional = true}
19
- appium-python-client = {version = "^4.0.0", optional = true}
20
18
  uiautomator2 = ">=2"
21
19
  httpx = "*"
22
20
  fastapi = "^0.111.0"
23
21
  uvicorn = {version = "*", extras = ["standard"]}
24
22
  poetry = "^1.8.2"
25
23
  pydantic = "^2.6"
24
+ wdapy = "^0.2.2"
26
25
 
27
- [tool.poetry.extras]
28
- appium = ["appium-python-client", "httppretty"]
26
+ #[tool.poetry.extras]
27
+ #appium = ["appium-python-client", "httppretty"]
29
28
 
30
29
  [tool.poetry.scripts]
31
30
  "uiauto.dev" = "uiautodev.__main__:main"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Created on Mon Mar 04 2024 14:28:53 by codeskyblue
5
+ """
6
+
7
+ # version is auto managed by poetry
8
+ __version__ = "0.5.0"
@@ -140,7 +140,7 @@ def self_update():
140
140
  @click.option("-f", "--force", is_flag=True, default=False, help="shutdown alrealy runningserver")
141
141
  @click.option("-s", "--no-browser", is_flag=True, default=False, help="silent mode, do not open browser")
142
142
  def server(port: int, host: str, reload: bool, force: bool, no_browser: bool):
143
- logger.info("version: %s", __version__)
143
+ print("uiautodev version:", __version__)
144
144
  if force:
145
145
  try:
146
146
  httpx.get(f"http://{host}:{port}/shutdown", timeout=3)
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  import time
10
10
  import typing
11
- from typing import Callable, Dict, Optional
11
+ from typing import Callable, Dict, List, Optional, Union
12
12
 
13
13
  from pydantic import BaseModel
14
14
 
@@ -17,7 +17,7 @@ from uiautodev.command_types import AppLaunchRequest, AppTerminateRequest, By, C
17
17
  WindowSizeResponse
18
18
  from uiautodev.driver.base_driver import BaseDriver
19
19
  from uiautodev.exceptions import ElementNotFoundError
20
- from uiautodev.model import Node
20
+ from uiautodev.model import Node, AppInfo
21
21
  from uiautodev.utils.common import node_travel
22
22
 
23
23
  COMMANDS: Dict[Command, Callable] = {}
@@ -39,17 +39,21 @@ def get_command_params_type(command: Command) -> Optional[BaseModel]:
39
39
  return type_hints.get("params")
40
40
 
41
41
 
42
- def send_command(driver: BaseDriver, command: Command, params=None):
42
+ def send_command(driver: BaseDriver, command: Union[str, Command], params=None):
43
43
  if command not in COMMANDS:
44
44
  raise NotImplementedError(f"command {command} not implemented")
45
45
  func = COMMANDS[command]
46
- type_hints = typing.get_type_hints(func)
47
- if type_hints.get("params"):
46
+ params_model = get_command_params_type(command)
47
+ if params_model:
48
48
  if params is None:
49
49
  raise ValueError(f"params is required for {command}")
50
- if not isinstance(params, type_hints["params"]):
51
- raise TypeError(f"params should be {type_hints['params']}")
52
- if params is None:
50
+ if isinstance(params, dict):
51
+ params = params_model.model_validate(params)
52
+ elif isinstance(params, params_model):
53
+ pass
54
+ else:
55
+ raise TypeError(f"params should be {params_model}", params)
56
+ if not params:
53
57
  return func(driver)
54
58
  return func(driver, params)
55
59
 
@@ -105,6 +109,31 @@ def home(driver: BaseDriver):
105
109
  driver.home()
106
110
 
107
111
 
112
+ @register(Command.BACK)
113
+ def back(driver: BaseDriver):
114
+ driver.back()
115
+
116
+
117
+ @register(Command.APP_SWITCH)
118
+ def app_switch(driver: BaseDriver):
119
+ driver.app_switch()
120
+
121
+
122
+ @register(Command.VOLUME_UP)
123
+ def volume_up(driver: BaseDriver):
124
+ driver.volume_up()
125
+
126
+
127
+ @register(Command.VOLUME_DOWN)
128
+ def volume_down(driver: BaseDriver):
129
+ driver.volume_down()
130
+
131
+
132
+ @register(Command.VOLUME_MUTE)
133
+ def volume_mute(driver: BaseDriver):
134
+ driver.volume_mute()
135
+
136
+
108
137
  @register(Command.DUMP)
109
138
  def dump(driver: BaseDriver) -> DumpResponse:
110
139
  source, _ = driver.dump_hierarchy()
@@ -152,3 +181,10 @@ def click_element(driver: BaseDriver, params: FindElementRequest):
152
181
  center_x = (node.bounds[0] + node.bounds[2]) / 2
153
182
  center_y = (node.bounds[1] + node.bounds[3]) / 2
154
183
  tap(driver, TapRequest(x=center_x, y=center_y, isPercent=True))
184
+
185
+
186
+ @register(Command.APP_LIST)
187
+ def app_list(driver: BaseDriver) -> List[AppInfo]:
188
+ # added in v0.5.0
189
+ return driver.app_list()
190
+
@@ -22,6 +22,7 @@ class Command(str, enum.Enum):
22
22
  APP_CURRENT = "currentApp"
23
23
  APP_LAUNCH = "appLaunch"
24
24
  APP_TERMINATE = "appTerminate"
25
+ APP_LIST = "appList"
25
26
 
26
27
  GET_WINDOW_SIZE = "getWindowSize"
27
28
  HOME = "home"
@@ -32,6 +33,13 @@ class Command(str, enum.Enum):
32
33
 
33
34
  LIST = "list"
34
35
 
36
+ # 0.4.0
37
+ BACK = "back"
38
+ APP_SWITCH = "appSwitch"
39
+ VOLUME_UP = "volumeUp"
40
+ VOLUME_DOWN = "volumeDown"
41
+ VOLUME_MUTE = "volumeMute"
42
+
35
43
 
36
44
  class TapRequest(BaseModel):
37
45
  x: Union[int, float]
@@ -4,12 +4,11 @@
4
4
  """Created on Fri Mar 01 2024 14:19:29 by codeskyblue
5
5
  """
6
6
 
7
- import json
8
7
  import logging
9
8
  import re
10
9
  import time
11
10
  from functools import cached_property, partial
12
- from typing import List, Optional, Tuple
11
+ from typing import Iterator, List, Optional, Tuple
13
12
  from xml.etree import ElementTree
14
13
 
15
14
  import adbutils
@@ -18,9 +17,8 @@ from PIL import Image
18
17
 
19
18
  from uiautodev.command_types import CurrentAppResponse
20
19
  from uiautodev.driver.base_driver import BaseDriver
21
- from uiautodev.driver.udt.udt import UDT, UDTError
22
20
  from uiautodev.exceptions import AndroidDriverException, RequestError
23
- from uiautodev.model import Node, Rect, ShellResponse, WindowSize
21
+ from uiautodev.model import Node, AppInfo, Rect, ShellResponse, WindowSize
24
22
  from uiautodev.utils.common import fetch_through_socket
25
23
 
26
24
  logger = logging.getLogger(__name__)
@@ -29,28 +27,15 @@ class AndroidDriver(BaseDriver):
29
27
  def __init__(self, serial: str):
30
28
  super().__init__(serial)
31
29
  self.adb_device = adbutils.device(serial)
32
- self._try_dump_list = [
33
- self._get_u2_hierarchy,
34
- self._get_udt_dump_hierarchy,
35
- # self._get_appium_hierarchy,
36
- ]
37
-
38
- @cached_property
39
- def udt(self) -> UDT:
40
- return UDT(self.adb_device)
41
30
 
42
31
  @cached_property
43
32
  def ud(self) -> u2.Device:
44
33
  return u2.connect_usb(self.serial)
45
34
 
46
35
  def screenshot(self, id: int) -> Image.Image:
47
- try:
48
- return self.adb_device.screenshot() # display_id is not OK now
49
- except adbutils.AdbError as e:
50
- logger.warning("screenshot error: %s", str(e))
51
- if id > 0:
52
- raise AndroidDriverException("multi-display is not supported yet for uiautomator2")
53
- return self.ud.screenshot()
36
+ if id > 0:
37
+ raise AndroidDriverException("multi-display is not supported yet for uiautomator2")
38
+ return self.ud.screenshot()
54
39
 
55
40
  def shell(self, command: str) -> ShellResponse:
56
41
  try:
@@ -82,38 +67,12 @@ class AndroidDriver(BaseDriver):
82
67
 
83
68
  uiautomator dump errors:
84
69
  - ERROR: could not get idle state.
85
-
86
70
  """
87
- for dump_func in self._try_dump_list[:]:
88
- try:
89
- logger.debug(f"try to dump with %s", dump_func.__name__)
90
- result = dump_func()
91
- logger.debug("dump success")
92
- self._try_dump_list.remove(dump_func)
93
- self._try_dump_list.insert(0, dump_func)
94
- return result
95
- except Exception as e:
96
- logger.exception("unexpected dump error: %s", e)
97
- raise AndroidDriverException("Failed to dump hierarchy")
98
-
99
- def _get_u2_hierarchy(self) -> str:
100
- d = u2.connect_usb(self.serial)
101
- return d.dump_hierarchy()
102
-
103
- def _get_appium_hierarchy(self) -> str:
104
- c = self.adb_device.create_connection(adbutils.Network.TCP, 6790)
105
71
  try:
106
- content = fetch_through_socket(c, "/wd/hub/session/0/source", timeout=10)
107
- return json.loads(content)["value"]
108
- except (adbutils.AdbError, RequestError) as e:
109
- raise AndroidDriverException(
110
- f"Failed to get hierarchy from appium server: {str(e)}"
111
- )
112
- finally:
113
- c.close()
114
-
115
- def _get_udt_dump_hierarchy(self) -> str:
116
- return self.udt.dump_hierarchy()
72
+ return self.ud.dump_hierarchy()
73
+ except Exception as e:
74
+ logger.exception("unexpected dump error: %s", e)
75
+ raise AndroidDriverException("Failed to dump hierarchy")
117
76
 
118
77
  def tap(self, x: int, y: int):
119
78
  self.adb_device.click(x, y)
@@ -144,6 +103,37 @@ class AndroidDriver(BaseDriver):
144
103
 
145
104
  def wake_up(self):
146
105
  self.adb_device.keyevent("WAKEUP")
106
+
107
+ def back(self):
108
+ self.adb_device.keyevent("BACK")
109
+
110
+ def app_switch(self):
111
+ self.adb_device.keyevent("APP_SWITCH")
112
+
113
+ def volume_up(self):
114
+ self.adb_device.keyevent("VOLUME_UP")
115
+
116
+ def volume_down(self):
117
+ self.adb_device.keyevent("VOLUME_DOWN")
118
+
119
+ def volume_mute(self):
120
+ self.adb_device.keyevent("VOLUME_MUTE")
121
+
122
+ def app_list(self) -> List[AppInfo]:
123
+ results = []
124
+ output = self.adb_device.shell(["pm", "list", "packages", '-3'])
125
+ for m in re.finditer(r"^package:([^\s]+)\r?$", output, re.M):
126
+ packageName = m.group(1)
127
+ results.append(AppInfo(packageName=packageName))
128
+ return results
129
+
130
+ def open_app_file(self, package: str) -> Iterator[bytes]:
131
+ line = self.adb_device.shell(f"pm path {package}")
132
+ if not line.startswith("package:"):
133
+ raise AndroidDriverException(f"Failed to get package path: {line}")
134
+ remote_path = line.split(':', 1)[1]
135
+ yield from self.adb_device.sync.iter_content(remote_path)
136
+
147
137
 
148
138
 
149
139
  def parse_xml(xml_data: str, wsize: WindowSize, display_id: Optional[int] = None) -> Node:
@@ -4,14 +4,14 @@
4
4
  """Created on Fri Mar 01 2024 14:18:30 by codeskyblue
5
5
  """
6
6
  import abc
7
- import enum
8
- from typing import Tuple
7
+ from io import FileIO
8
+ from typing import Iterator, List, Tuple
9
9
 
10
10
  from PIL import Image
11
11
  from pydantic import BaseModel
12
12
 
13
13
  from uiautodev.command_types import CurrentAppResponse
14
- from uiautodev.model import Node, ShellResponse, WindowSize
14
+ from uiautodev.model import Node, AppInfo, ShellResponse, WindowSize
15
15
 
16
16
 
17
17
  class BaseDriver(abc.ABC):
@@ -70,7 +70,36 @@ class BaseDriver(abc.ABC):
70
70
  def home(self):
71
71
  """ press home button """
72
72
  raise NotImplementedError()
73
+
74
+ def back(self):
75
+ """ press back button """
76
+ raise NotImplementedError()
77
+
78
+ def app_switch(self):
79
+ """ switch app """
80
+ raise NotImplementedError()
81
+
82
+ def volume_up(self):
83
+ """ volume up """
84
+ raise NotImplementedError()
85
+
86
+ def volume_down(self):
87
+ """ volume down """
88
+ raise NotImplementedError()
89
+
90
+ def volume_mute(self):
91
+ """ volume mute """
92
+ raise NotImplementedError()
73
93
 
74
94
  def wake_up(self):
75
95
  """ wake up the device """
76
- raise NotImplementedError()
96
+ raise NotImplementedError()
97
+
98
+ def app_list(self) -> List[AppInfo]:
99
+ """ list installed packages """
100
+ raise NotImplementedError()
101
+
102
+ def open_app_file(self, package: str) -> Iterator[bytes]:
103
+ """ open app file """
104
+ raise NotImplementedError()
105
+
@@ -5,21 +5,19 @@
5
5
  """
6
6
 
7
7
 
8
- import base64
9
- import io
10
8
  import json
11
- import re
12
9
  from functools import partial
13
10
  from typing import List, Optional, Tuple
14
11
  from xml.etree import ElementTree
15
12
 
13
+ import wdapy
16
14
  from PIL import Image
17
15
 
18
16
  from uiautodev.command_types import CurrentAppResponse
19
17
  from uiautodev.driver.base_driver import BaseDriver
20
18
  from uiautodev.exceptions import IOSDriverException
21
19
  from uiautodev.model import Node, WindowSize
22
- from uiautodev.utils.usbmux import MuxDevice, select_device
20
+ from uiautodev.utils.usbmux import select_device
23
21
 
24
22
 
25
23
  class IOSDriver(BaseDriver):
@@ -27,6 +25,7 @@ class IOSDriver(BaseDriver):
27
25
  """ serial is the udid of the ios device """
28
26
  super().__init__(serial)
29
27
  self.device = select_device(serial)
28
+ self.wda = wdapy.AppiumUSBClient(self.device.serial)
30
29
 
31
30
  def _request(self, method: str, path: str, payload: Optional[dict] = None) -> bytes:
32
31
  conn = self.device.make_http_connection(port=8100)
@@ -56,29 +55,36 @@ class IOSDriver(BaseDriver):
56
55
  return self._request_json("GET", "/status")
57
56
 
58
57
  def screenshot(self, id: int = 0) -> Image.Image:
59
- png_base64 = self._request_json_value("GET", "/screenshot")
60
- png_data = base64.b64decode(png_base64)
61
- return Image.open(io.BytesIO(png_data))
58
+ return self.wda.screenshot()
62
59
 
63
60
  def window_size(self):
64
- return self._request_json_value("GET", "/window/size")
61
+ return self.wda.window_size()
65
62
 
66
63
  def dump_hierarchy(self) -> Tuple[str, Node]:
67
64
  """returns xml string and hierarchy object"""
68
- xml_data = self._request_json_value("GET", "/source")
65
+ t = self.wda.sourcetree()
66
+ xml_data = t.value
69
67
  root = ElementTree.fromstring(xml_data)
70
68
  return xml_data, parse_xml_element(root, WindowSize(width=1, height=1))
71
69
 
72
70
  def tap(self, x: int, y: int):
73
- self._request("POST", f"/wda/tap/0", {"x": x, "y": y})
71
+ self.wda.tap(x, y)
74
72
 
75
73
  def app_current(self) -> CurrentAppResponse:
76
- # {'processArguments': {'env': {}, 'args': []}, 'name': '', 'pid': 32, 'bundleId': 'com.apple.springboard'}
77
- value = self._request_json_value("GET", "/wda/activeAppInfo")
78
- return CurrentAppResponse(package=value["bundleId"], pid=value["pid"])
74
+ info = self.wda.app_current()
75
+ return CurrentAppResponse(package=info.bundle_id, pid=info.pid)
79
76
 
80
77
  def home(self):
81
- self._request("POST", "/wda/homescreen")
78
+ self.wda.homescreen()
79
+
80
+ def app_switch(self):
81
+ raise NotImplementedError()
82
+
83
+ def volume_up(self):
84
+ self.wda.volume_up()
85
+
86
+ def volume_down(self):
87
+ self.wda.volume_down()
82
88
 
83
89
 
84
90
  def parse_xml_element(element, wsize: WindowSize, indexes: List[int]=[0]) -> Node:
@@ -42,4 +42,8 @@ class Node(BaseModel):
42
42
 
43
43
  class WindowSize(typing.NamedTuple):
44
44
  width: int
45
- height: int
45
+ height: int
46
+
47
+
48
+ class AppInfo(BaseModel):
49
+ packageName: str
@@ -6,9 +6,10 @@
6
6
 
7
7
  import io
8
8
  import logging
9
- from typing import Any, List
9
+ from typing import Any, Dict, List
10
10
 
11
11
  from fastapi import APIRouter, Response
12
+ from fastapi.responses import StreamingResponse
12
13
  from pydantic import BaseModel
13
14
 
14
15
  from uiautodev import command_proxy
@@ -102,14 +103,25 @@ def make_router(provider: BaseProvider) -> APIRouter:
102
103
  return command_proxy.app_current(driver)
103
104
 
104
105
  @router.post('/{serial}/command/{command}')
105
- def _command_proxy_other(serial: str, command: Command, params: Any = None):
106
+ def _command_proxy_other(serial: str, command: Command, params: Dict[str, Any] = None):
106
107
  """Run a command on the device"""
107
108
  driver = provider.get_device_driver(serial)
108
- func = command_proxy.COMMANDS[command]
109
- if params is None:
110
- response = func(driver)
111
- else:
112
- response = func(driver, params)
109
+ response = command_proxy.send_command(driver, command, params)
113
110
  return response
111
+
112
+ @router.get('/{serial}/backupApp')
113
+ def _backup_app(serial: str, packageName: str):
114
+ """Backup app
115
+
116
+ Added in 0.5.0
117
+ """
118
+ driver = provider.get_device_driver(serial)
119
+ file_name = f"{packageName}.apk"
120
+ headers = {
121
+ 'Content-Disposition': f'attachment; filename="{file_name}"'
122
+ }
123
+ return StreamingResponse(driver.open_app_file(packageName), headers=headers)
124
+
125
+
114
126
 
115
127
  return router
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """Created on Mon Mar 04 2024 14:28:53 by codeskyblue
5
- """
6
-
7
- from importlib.metadata import PackageNotFoundError, version
8
-
9
- try:
10
- __version__ = version("uiautodev")
11
- except PackageNotFoundError:
12
- __version__ = "0.0.0"
File without changes
File without changes
File without changes
File without changes