pymobiledevice3 5.0.4__py3-none-any.whl → 7.0.6__py3-none-any.whl

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 (79) hide show
  1. misc/understanding_idevice_protocol_layers.md +10 -5
  2. pymobiledevice3/__main__.py +171 -46
  3. pymobiledevice3/_version.py +2 -2
  4. pymobiledevice3/bonjour.py +22 -21
  5. pymobiledevice3/cli/activation.py +24 -22
  6. pymobiledevice3/cli/afc.py +49 -41
  7. pymobiledevice3/cli/amfi.py +13 -18
  8. pymobiledevice3/cli/apps.py +71 -65
  9. pymobiledevice3/cli/backup.py +134 -93
  10. pymobiledevice3/cli/bonjour.py +31 -29
  11. pymobiledevice3/cli/cli_common.py +175 -232
  12. pymobiledevice3/cli/companion_proxy.py +12 -12
  13. pymobiledevice3/cli/crash.py +95 -52
  14. pymobiledevice3/cli/developer/__init__.py +62 -0
  15. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  16. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  17. pymobiledevice3/cli/developer/arbitration.py +50 -0
  18. pymobiledevice3/cli/developer/condition.py +33 -0
  19. pymobiledevice3/cli/developer/core_device.py +294 -0
  20. pymobiledevice3/cli/developer/debugserver.py +244 -0
  21. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  22. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  23. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  24. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  25. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  26. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  27. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  28. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  29. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  30. pymobiledevice3/cli/idam.py +42 -0
  31. pymobiledevice3/cli/lockdown.py +70 -75
  32. pymobiledevice3/cli/mounter.py +99 -57
  33. pymobiledevice3/cli/notification.py +38 -26
  34. pymobiledevice3/cli/pcap.py +36 -20
  35. pymobiledevice3/cli/power_assertion.py +15 -16
  36. pymobiledevice3/cli/processes.py +11 -17
  37. pymobiledevice3/cli/profile.py +120 -75
  38. pymobiledevice3/cli/provision.py +27 -26
  39. pymobiledevice3/cli/remote.py +109 -100
  40. pymobiledevice3/cli/restore.py +134 -129
  41. pymobiledevice3/cli/springboard.py +50 -50
  42. pymobiledevice3/cli/syslog.py +145 -65
  43. pymobiledevice3/cli/usbmux.py +66 -27
  44. pymobiledevice3/cli/version.py +2 -5
  45. pymobiledevice3/cli/webinspector.py +232 -156
  46. pymobiledevice3/exceptions.py +6 -2
  47. pymobiledevice3/lockdown.py +5 -1
  48. pymobiledevice3/lockdown_service_provider.py +5 -0
  49. pymobiledevice3/remote/remote_service_discovery.py +18 -10
  50. pymobiledevice3/restore/device.py +28 -4
  51. pymobiledevice3/restore/restore.py +2 -2
  52. pymobiledevice3/service_connection.py +15 -12
  53. pymobiledevice3/services/afc.py +731 -220
  54. pymobiledevice3/services/device_link.py +45 -31
  55. pymobiledevice3/services/idam.py +20 -0
  56. pymobiledevice3/services/lockdown_service.py +12 -9
  57. pymobiledevice3/services/mobile_config.py +1 -0
  58. pymobiledevice3/services/mobilebackup2.py +6 -3
  59. pymobiledevice3/services/os_trace.py +97 -55
  60. pymobiledevice3/services/remote_fetch_symbols.py +13 -8
  61. pymobiledevice3/services/screenshot.py +2 -2
  62. pymobiledevice3/services/web_protocol/alert.py +8 -8
  63. pymobiledevice3/services/web_protocol/automation_session.py +87 -79
  64. pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  65. pymobiledevice3/services/web_protocol/driver.py +71 -70
  66. pymobiledevice3/services/web_protocol/element.py +58 -56
  67. pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
  68. pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
  69. pymobiledevice3/services/web_protocol/switch_to.py +23 -19
  70. pymobiledevice3/services/webinspector.py +42 -67
  71. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
  72. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
  73. pymobiledevice3/cli/completions.py +0 -50
  74. pymobiledevice3/cli/developer.py +0 -1539
  75. pymobiledevice3/cli/diagnostics.py +0 -110
  76. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
  77. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  78. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
  79. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -2,41 +2,45 @@ import logging
2
2
  import plistlib
3
3
  import tempfile
4
4
  from pathlib import Path
5
- from typing import IO, Optional
5
+ from typing import Annotated, Literal, Optional
6
6
 
7
- import click
7
+ import typer
8
+ from typer_injector import InjectingTyper
8
9
 
9
10
  from pymobiledevice3.ca import create_keybag_file
10
- from pymobiledevice3.cli.cli_common import Command, print_json
11
- from pymobiledevice3.lockdown import LockdownClient
12
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
11
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
13
12
  from pymobiledevice3.services.mobile_activation import MobileActivationService
14
13
  from pymobiledevice3.services.mobile_config import MobileConfigService
15
14
 
16
15
  logger = logging.getLogger(__name__)
17
16
 
18
17
 
19
- @click.group()
20
- def cli() -> None:
21
- pass
22
-
23
-
24
- @cli.group("profile")
25
- def profile_group() -> None:
26
- """Managed installed profiles or install SSL certificates"""
27
- pass
18
+ cli = InjectingTyper(
19
+ name="profile",
20
+ help="Manage installed profiles or install SSL certificates",
21
+ no_args_is_help=True,
22
+ )
28
23
 
29
24
 
30
- @profile_group.command("list", cls=Command)
31
- def profile_list(service_provider: LockdownClient):
25
+ @cli.command("list")
26
+ def profile_list(service_provider: ServiceProviderDep) -> None:
32
27
  """List installed profiles"""
33
28
  print_json(MobileConfigService(lockdown=service_provider).get_profile_list())
34
29
 
35
30
 
36
- @profile_group.command("install", cls=Command)
37
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
38
- @click.argument("profiles", nargs=-1, type=click.File("rb"))
39
- def profile_install(service_provider: LockdownServiceProvider, keybag: Optional[str], profiles: list[IO]) -> None:
31
+ @cli.command("install")
32
+ def profile_install(
33
+ service_provider: ServiceProviderDep,
34
+ profiles: list[Path],
35
+ keybag: Annotated[
36
+ Optional[Path],
37
+ typer.Option(
38
+ exists=True,
39
+ file_okay=True,
40
+ dir_okay=False,
41
+ ),
42
+ ] = None,
43
+ ) -> None:
40
44
  """
41
45
  Install given profiles
42
46
 
@@ -44,61 +48,58 @@ def profile_install(service_provider: LockdownServiceProvider, keybag: Optional[
44
48
  """
45
49
  service = MobileConfigService(lockdown=service_provider)
46
50
  for profile in profiles:
47
- logger.info(f"installing {profile.name}")
51
+ logger.info(f"installing {profile}")
48
52
  if keybag is not None:
49
- service.install_profile_silent(Path(keybag), profile.read())
53
+ service.install_profile_silent(Path(keybag), profile.read_bytes())
50
54
  else:
51
- service.install_profile(profile.read())
55
+ service.install_profile(profile.read_bytes())
52
56
 
53
57
 
54
- @profile_group.command("cloud-configuration", cls=Command)
55
- @click.argument("config", type=click.File("rb"), required=False)
56
- def profile_cloud_configuration(service_provider: LockdownServiceProvider, config: Optional[IO]) -> None:
58
+ @cli.command("cloud-configuration")
59
+ def profile_cloud_configuration(service_provider: ServiceProviderDep, config: Optional[Path] = None) -> None:
57
60
  """Get/Set cloud configuration"""
58
61
  if not config:
59
62
  print_json(MobileConfigService(lockdown=service_provider).get_cloud_configuration())
60
63
  else:
61
- config_json = plistlib.load(config)
64
+ with config.open("rb") as config_file:
65
+ config_json = plistlib.load(config_file)
62
66
  logger.info(f"applying cloud configuration {config_json}")
63
67
  MobileConfigService(lockdown=service_provider).set_cloud_configuration(config_json)
64
68
  logger.info("applied cloud configuration")
65
69
 
66
70
 
67
- @profile_group.command("store", cls=Command)
68
- @click.argument("profiles", nargs=-1, type=click.File("rb"))
69
- def profile_store(service_provider: LockdownServiceProvider, profiles: list[IO]) -> None:
71
+ @cli.command("store")
72
+ def profile_store(service_provider: ServiceProviderDep, profiles: list[Path]) -> None:
70
73
  """Store a profile"""
71
74
  service = MobileConfigService(lockdown=service_provider)
72
75
  for profile in profiles:
73
76
  logger.info(f"storing {profile.name}")
74
- service.store_profile(profile.read())
77
+ service.store_profile(profile.read_bytes())
75
78
 
76
79
 
77
- @profile_group.command("remove", cls=Command)
78
- @click.argument("name")
79
- def profile_remove(service_provider: LockdownServiceProvider, name: str) -> None:
80
+ @cli.command("remove")
81
+ def profile_remove(service_provider: ServiceProviderDep, name: str) -> None:
80
82
  """Remove a profile by its name"""
81
83
  MobileConfigService(lockdown=service_provider).remove_profile(name)
82
84
 
83
85
 
84
- @profile_group.command("set-wifi-power", cls=Command)
85
- @click.argument("state", type=click.Choice(["on", "off"]), required=False)
86
- def profile_set_wifi_power(service_provider: LockdownServiceProvider, state: str) -> None:
86
+ @cli.command("set-wifi-power")
87
+ def profile_set_wifi_power(service_provider: ServiceProviderDep, state: Literal["on", "off"] = "off") -> None:
87
88
  """change Wi-Fi power state"""
88
89
  MobileConfigService(lockdown=service_provider).set_wifi_power_state(state == "on")
89
90
 
90
91
 
91
- @profile_group.command("erase-device", cls=Command)
92
- @click.option(
93
- "--preserve-data-plan/--no-preserve-data-plan", default=True, help="Preserves eSIM / data plan after erase"
94
- )
95
- @click.option(
96
- "--disallow-proximity-setup/--no-disallow-proximity-setup",
97
- default=False,
98
- help="Disallows to setup the erased device from nearby devices",
99
- )
92
+ @cli.command("erase-device")
100
93
  def profile_erase_device(
101
- service_provider: LockdownServiceProvider, preserve_data_plan: bool, disallow_proximity_setup: bool
94
+ service_provider: ServiceProviderDep,
95
+ preserve_data_plan: Annotated[
96
+ bool,
97
+ typer.Option(help="Preserves eSIM / data plan after erase"),
98
+ ] = True,
99
+ disallow_proximity_setup: Annotated[
100
+ bool,
101
+ typer.Option(help="Disallows setup of the erased device from nearby devices"),
102
+ ] = False,
102
103
  ) -> None:
103
104
  """Erase device"""
104
105
  logger.info(
@@ -109,18 +110,35 @@ def profile_erase_device(
109
110
  logger.info("Erased device")
110
111
 
111
112
 
112
- @profile_group.command("create-keybag")
113
- @click.argument("keybag", type=click.Path(file_okay=True, dir_okay=False, exists=False))
114
- @click.argument("organization")
115
- def profile_create_keybag(keybag: str, organization: str) -> None:
113
+ @cli.command("create-keybag")
114
+ def profile_create_keybag(
115
+ keybag: Annotated[
116
+ Path,
117
+ typer.Argument(
118
+ exists=False,
119
+ file_okay=True,
120
+ dir_okay=False,
121
+ ),
122
+ ],
123
+ organization: str,
124
+ ) -> None:
116
125
  """Create keybag storing certificate and private key"""
117
- create_keybag_file(Path(keybag), organization)
118
-
119
-
120
- @profile_group.command("supervise", cls=Command)
121
- @click.argument("organization")
122
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
123
- def profile_supervise(service_provider: LockdownServiceProvider, organization: str, keybag: Optional[str]) -> None:
126
+ create_keybag_file(keybag, organization)
127
+
128
+
129
+ @cli.command("supervise")
130
+ def profile_supervise(
131
+ service_provider: ServiceProviderDep,
132
+ organization: str,
133
+ keybag: Annotated[
134
+ Optional[Path],
135
+ typer.Option(
136
+ exists=True,
137
+ file_okay=True,
138
+ dir_okay=False,
139
+ ),
140
+ ] = None,
141
+ ) -> None:
124
142
  """Supervise device"""
125
143
  if MobileActivationService(service_provider).state == "Unactivated":
126
144
  logger.info("Activating device")
@@ -138,13 +156,20 @@ def profile_supervise(service_provider: LockdownServiceProvider, organization: s
138
156
  logger.info("Device has been successfully supervised")
139
157
 
140
158
 
141
- @profile_group.command("install-wifi-profile", cls=Command)
142
- @click.argument("encryption_type")
143
- @click.argument("ssid")
144
- @click.argument("password")
145
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
159
+ @cli.command("install-wifi-profile")
146
160
  def profile_install_wifi_profile(
147
- service_provider: LockdownServiceProvider, encryption_type: str, ssid: str, password: str, keybag: Optional[str]
161
+ service_provider: ServiceProviderDep,
162
+ encryption_type: str,
163
+ ssid: str,
164
+ password: str,
165
+ keybag: Annotated[
166
+ Optional[Path],
167
+ typer.Option(
168
+ exists=True,
169
+ file_okay=True,
170
+ dir_okay=False,
171
+ ),
172
+ ] = None,
148
173
  ) -> None:
149
174
  """
150
175
  Install Wi-Fi profile
@@ -158,12 +183,22 @@ def profile_install_wifi_profile(
158
183
  )
159
184
 
160
185
 
161
- @profile_group.command("install-http-proxy", cls=Command)
162
- @click.argument("server")
163
- @click.argument("port", type=click.IntRange(1, 65535))
164
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
186
+ @cli.command("install-http-proxy")
165
187
  def profile_install_http_proxy(
166
- service_provider: LockdownServiceProvider, server: str, port: int, keybag: Optional[str]
188
+ service_provider: ServiceProviderDep,
189
+ server: str,
190
+ port: Annotated[
191
+ int,
192
+ typer.Argument(min=1, max=0xFFFF),
193
+ ],
194
+ keybag: Annotated[
195
+ Optional[Path],
196
+ typer.Option(
197
+ exists=True,
198
+ file_okay=True,
199
+ dir_okay=False,
200
+ ),
201
+ ] = None,
167
202
  ) -> None:
168
203
  """Install HTTP Proxy profile"""
169
204
  if keybag is not None:
@@ -171,17 +206,27 @@ def profile_install_http_proxy(
171
206
  MobileConfigService(lockdown=service_provider).install_http_proxy(server, port, keybag_file=keybag)
172
207
 
173
208
 
174
- @profile_group.command("remove-http-proxy", cls=Command)
175
- def profile_remove_http_proxy(service_provider: LockdownServiceProvider) -> None:
209
+ @cli.command("remove-http-proxy")
210
+ def profile_remove_http_proxy(service_provider: ServiceProviderDep) -> None:
176
211
  """Remove HTTP Proxy profile that was previously installed using pymobiledevice3"""
177
212
  MobileConfigService(lockdown=service_provider).remove_http_proxy()
178
213
 
179
214
 
180
- @profile_group.command("install-restrictions-profile", cls=Command)
181
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
182
- @click.option("--enforced-software-update-delay", type=click.IntRange(0, 90), default=0)
215
+ @cli.command("install-restrictions-profile")
183
216
  def profile_install_restrictions_profile(
184
- service_provider: LockdownServiceProvider, keybag: Optional[str], enforced_software_update_delay: int
217
+ service_provider: ServiceProviderDep,
218
+ keybag: Annotated[
219
+ Optional[Path],
220
+ typer.Option(
221
+ exists=True,
222
+ file_okay=True,
223
+ dir_okay=False,
224
+ ),
225
+ ] = None,
226
+ enforced_software_update_delay: Annotated[
227
+ int,
228
+ typer.Option(min=0, max=90),
229
+ ] = 0,
185
230
  ) -> None:
186
231
  """Install restrictions profile (can be used for delayed OTA)"""
187
232
  if keybag is not None:
@@ -1,56 +1,57 @@
1
1
  import logging
2
2
  from pathlib import Path
3
+ from typing import Annotated
3
4
 
4
- import click
5
+ import typer
6
+ from typer_injector import InjectingTyper
5
7
 
6
- from pymobiledevice3.cli.cli_common import Command, print_json
7
- from pymobiledevice3.lockdown import LockdownClient
8
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
8
9
  from pymobiledevice3.services.misagent import MisagentService
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
13
 
13
- @click.group()
14
- def cli() -> None:
15
- pass
14
+ cli = InjectingTyper(
15
+ name="provision",
16
+ help="Manage installed provision profiles",
17
+ no_args_is_help=True,
18
+ )
16
19
 
17
20
 
18
- @cli.group()
19
- def provision() -> None:
20
- """Manage installed provision profiles"""
21
- pass
22
-
23
-
24
- @provision.command("install", cls=Command)
25
- @click.argument("profile", type=click.File("rb"))
26
- def provision_install(service_provider: LockdownClient, profile):
21
+ @cli.command("install")
22
+ def provision_install(service_provider: ServiceProviderDep, profile: Path) -> None:
27
23
  """install a provision profile (.mobileprovision file)"""
28
- MisagentService(lockdown=service_provider).install(profile)
24
+ with profile.open("rb") as profile_file:
25
+ MisagentService(lockdown=service_provider).install(profile_file)
29
26
 
30
27
 
31
- @provision.command("remove", cls=Command)
32
- @click.argument("profile_id")
33
- def provision_remove(service_provider: LockdownClient, profile_id):
28
+ @cli.command("remove")
29
+ def provision_remove(service_provider: ServiceProviderDep, profile_id: str) -> None:
34
30
  """remove a provision profile"""
35
31
  MisagentService(lockdown=service_provider).remove(profile_id)
36
32
 
37
33
 
38
- @provision.command("clear", cls=Command)
39
- def provision_clear(service_provider: LockdownClient):
34
+ @cli.command("clear")
35
+ def provision_clear(service_provider: ServiceProviderDep) -> None:
40
36
  """remove all provision profiles"""
41
37
  for profile in MisagentService(lockdown=service_provider).copy_all():
42
38
  MisagentService(lockdown=service_provider).remove(profile.plist["UUID"])
43
39
 
44
40
 
45
- @provision.command("list", cls=Command)
46
- def provision_list(service_provider: LockdownClient):
41
+ @cli.command("list")
42
+ def provision_list(service_provider: ServiceProviderDep) -> None:
47
43
  """list installed provision profiles"""
48
44
  print_json([p.plist for p in MisagentService(lockdown=service_provider).copy_all()])
49
45
 
50
46
 
51
- @provision.command("dump", cls=Command)
52
- @click.argument("out", type=click.Path(file_okay=False, dir_okay=True, exists=True))
53
- def provision_dump(service_provider: LockdownClient, out):
47
+ @cli.command("dump")
48
+ def provision_dump(
49
+ service_provider: ServiceProviderDep,
50
+ out: Annotated[
51
+ Path,
52
+ typer.Argument(file_okay=False, dir_okay=True, exists=True),
53
+ ],
54
+ ) -> None:
54
55
  """dump installed provision profiles to specified location"""
55
56
  for profile in MisagentService(lockdown=service_provider).copy_all():
56
57
  filename = f"{profile.plist['UUID']}.mobileprovision"