uiprotect 0.1.0__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.

Potentially problematic release.


This version of uiprotect might be problematic. Click here for more details.

uiprotect/cli/base.py ADDED
@@ -0,0 +1,238 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Coroutine, Mapping, Sequence
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import Any, Optional, TypeVar
7
+
8
+ import orjson
9
+ import typer
10
+ from pydantic.v1 import ValidationError
11
+
12
+ from uiprotect.api import ProtectApiClient
13
+ from uiprotect.data import NVR, ProtectAdoptableDeviceModel, ProtectBaseObject
14
+ from uiprotect.exceptions import BadRequest, NvrError, StreamError
15
+ from uiprotect.utils import run_async
16
+
17
+ T = TypeVar("T")
18
+
19
+ OPTION_FORCE = typer.Option(False, "-f", "--force", help="Skip confirmation prompt")
20
+
21
+
22
+ class OutputFormatEnum(str, Enum):
23
+ JSON = "json"
24
+ PLAIN = "plain"
25
+
26
+
27
+ @dataclass
28
+ class CliContext:
29
+ protect: ProtectApiClient
30
+ output_format: OutputFormatEnum
31
+
32
+
33
+ def run(ctx: typer.Context, func: Coroutine[Any, Any, T]) -> T:
34
+ """Helper method to call async function and clean up API client"""
35
+
36
+ async def callback() -> T:
37
+ return_value = await func
38
+ await ctx.obj.protect.close_session()
39
+ return return_value
40
+
41
+ try:
42
+ return run_async(callback())
43
+ except (BadRequest, ValidationError, StreamError, NvrError) as err:
44
+ typer.secho(str(err), fg="red")
45
+ raise typer.Exit(1) from err
46
+
47
+
48
+ def json_output(obj: Any) -> None:
49
+ typer.echo(orjson.dumps(obj, option=orjson.OPT_INDENT_2).decode("utf-8"))
50
+
51
+
52
+ def print_unifi_obj(
53
+ obj: ProtectBaseObject | None,
54
+ output_format: OutputFormatEnum,
55
+ ) -> None:
56
+ """Helper method to print a single protect object"""
57
+ if obj is not None:
58
+ json_output(obj.unifi_dict())
59
+ elif output_format == OutputFormatEnum.JSON:
60
+ json_output(None)
61
+
62
+
63
+ def print_unifi_list(objs: Sequence[ProtectBaseObject]) -> None:
64
+ """Helper method to print a list of protect objects"""
65
+ data = [o.unifi_dict() for o in objs]
66
+ json_output(data)
67
+
68
+
69
+ def print_unifi_dict(objs: Mapping[str, ProtectBaseObject]) -> None:
70
+ """Helper method to print a dictionary of protect objects"""
71
+ data = {k: v.unifi_dict() for k, v in objs.items()}
72
+ json_output(data)
73
+
74
+
75
+ def require_device_id(ctx: typer.Context) -> None:
76
+ """Requires device ID in context"""
77
+ if ctx.obj.device is None:
78
+ typer.secho("Requires a valid device ID to be selected")
79
+ raise typer.Exit(1)
80
+
81
+
82
+ def require_no_device_id(ctx: typer.Context) -> None:
83
+ """Requires no device ID in context"""
84
+ if ctx.obj.device is not None:
85
+ typer.secho("Requires no device ID to be selected")
86
+ raise typer.Exit(1)
87
+
88
+
89
+ def list_ids(ctx: typer.Context) -> None:
90
+ """Requires no device ID. Prints list of "id name" for each device."""
91
+ require_no_device_id(ctx)
92
+ objs: dict[str, ProtectAdoptableDeviceModel] = ctx.obj.devices
93
+ to_print: list[tuple[str, str | None]] = []
94
+ for obj in objs.values():
95
+ name = obj.display_name
96
+ if obj.is_adopted_by_other:
97
+ name = f"{name} [Managed by Another Console]"
98
+ elif obj.is_adopting:
99
+ name = f"{name} [Adopting]"
100
+ elif obj.can_adopt:
101
+ name = f"{name} [Unadopted]"
102
+ elif obj.is_rebooting:
103
+ name = f"{name} [Restarting]"
104
+ elif obj.is_updating:
105
+ name = f"{name} [Updating]"
106
+ elif not obj.is_connected:
107
+ name = f"{name} [Disconnected]"
108
+
109
+ to_print.append((obj.id, name))
110
+
111
+ if ctx.obj.output_format == OutputFormatEnum.JSON:
112
+ json_output(to_print)
113
+ else:
114
+ for item in to_print:
115
+ typer.echo(f"{item[0]}\t{item[1]}")
116
+
117
+
118
+ def protect_url(ctx: typer.Context) -> None:
119
+ """Gets UniFi Protect management URL."""
120
+ require_device_id(ctx)
121
+ obj: NVR | ProtectAdoptableDeviceModel = ctx.obj.device
122
+ if ctx.obj.output_format == OutputFormatEnum.JSON:
123
+ json_output(obj.protect_url)
124
+ else:
125
+ typer.echo(obj.protect_url)
126
+
127
+
128
+ def is_wired(ctx: typer.Context) -> None:
129
+ """Returns if the device is wired or not."""
130
+ require_device_id(ctx)
131
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
132
+ json_output(obj.is_wired)
133
+
134
+
135
+ def is_wifi(ctx: typer.Context) -> None:
136
+ """Returns if the device has WiFi or not."""
137
+ require_device_id(ctx)
138
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
139
+ json_output(obj.is_wifi)
140
+
141
+
142
+ def is_bluetooth(ctx: typer.Context) -> None:
143
+ """Returns if the device has Bluetooth or not."""
144
+ require_device_id(ctx)
145
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
146
+ json_output(obj.is_bluetooth)
147
+
148
+
149
+ def bridge(ctx: typer.Context) -> None:
150
+ """Returns bridge device if connected via Bluetooth."""
151
+ require_device_id(ctx)
152
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
153
+ print_unifi_obj(obj.bridge, ctx.obj.output_format)
154
+
155
+
156
+ def set_ssh(ctx: typer.Context, enabled: bool) -> None:
157
+ """
158
+ Sets the isSshEnabled value for device.
159
+
160
+ May not have an effect on many device types. Only seems to work for
161
+ Linux and BusyBox based devices (camera, light and viewport).
162
+ """
163
+ require_device_id(ctx)
164
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
165
+ run(ctx, obj.set_ssh(enabled))
166
+
167
+
168
+ def set_name(ctx: typer.Context, name: Optional[str] = typer.Argument(None)) -> None:
169
+ """Sets name for the device"""
170
+ require_device_id(ctx)
171
+ obj: NVR | ProtectAdoptableDeviceModel = ctx.obj.device
172
+ run(ctx, obj.set_name(name))
173
+
174
+
175
+ def update(ctx: typer.Context, data: str) -> None:
176
+ """
177
+ Updates the device.
178
+
179
+ Makes a raw PATCH request to update a device. Advanced usage and usually recommended not to use.
180
+ """
181
+ require_device_id(ctx)
182
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
183
+
184
+ if obj.model is not None:
185
+ run(ctx, obj.api.update_device(obj.model, obj.id, orjson.loads(data)))
186
+
187
+
188
+ def reboot(ctx: typer.Context, force: bool = OPTION_FORCE) -> None:
189
+ """Reboots the device."""
190
+ require_device_id(ctx)
191
+ obj: NVR | ProtectAdoptableDeviceModel = ctx.obj.device
192
+
193
+ if force or typer.confirm(f'Confirm reboot of "{obj.name}"" (id: {obj.id})'):
194
+ run(ctx, obj.reboot())
195
+
196
+
197
+ def unadopt(ctx: typer.Context, force: bool = OPTION_FORCE) -> None:
198
+ """Unadopt/Unmanage adopted device."""
199
+ require_device_id(ctx)
200
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
201
+
202
+ if force or typer.confirm(f'Confirm undopt of "{obj.name}"" (id: {obj.id})'):
203
+ run(ctx, obj.unadopt())
204
+
205
+
206
+ def adopt(ctx: typer.Context, name: Optional[str] = typer.Argument(None)) -> None:
207
+ """
208
+ Adopts a device.
209
+
210
+ By default, unadopted devices do not show up in the bootstrap. Use
211
+ `unifi-protect -u` to show unadopted devices.
212
+ """
213
+ require_device_id(ctx)
214
+ obj: ProtectAdoptableDeviceModel = ctx.obj.device
215
+
216
+ run(ctx, obj.adopt(name))
217
+
218
+
219
+ def init_common_commands(
220
+ app: typer.Typer,
221
+ ) -> tuple[dict[str, Callable[..., Any]], dict[str, Callable[..., Any]]]:
222
+ deviceless_commands: dict[str, Callable[..., Any]] = {}
223
+ device_commands: dict[str, Callable[..., Any]] = {}
224
+
225
+ deviceless_commands["list-ids"] = app.command()(list_ids)
226
+ device_commands["protect-url"] = app.command()(protect_url)
227
+ device_commands["is-wired"] = app.command()(is_wired)
228
+ device_commands["is-wifi"] = app.command()(is_wifi)
229
+ device_commands["is-bluetooth"] = app.command()(is_bluetooth)
230
+ device_commands["bridge"] = app.command()(bridge)
231
+ device_commands["set-ssh"] = app.command()(set_ssh)
232
+ device_commands["set-name"] = app.command()(set_name)
233
+ device_commands["update"] = app.command()(update)
234
+ device_commands["reboot"] = app.command()(reboot)
235
+ device_commands["unadopt"] = app.command()(unadopt)
236
+ device_commands["adopt"] = app.command()(adopt)
237
+
238
+ return deviceless_commands, device_commands