eeroctl 1.7.1__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.
- eeroctl/__init__.py +19 -0
- eeroctl/commands/__init__.py +32 -0
- eeroctl/commands/activity.py +237 -0
- eeroctl/commands/auth.py +471 -0
- eeroctl/commands/completion.py +142 -0
- eeroctl/commands/device.py +492 -0
- eeroctl/commands/eero/__init__.py +12 -0
- eeroctl/commands/eero/base.py +224 -0
- eeroctl/commands/eero/led.py +154 -0
- eeroctl/commands/eero/nightlight.py +235 -0
- eeroctl/commands/eero/updates.py +82 -0
- eeroctl/commands/network/__init__.py +18 -0
- eeroctl/commands/network/advanced.py +191 -0
- eeroctl/commands/network/backup.py +162 -0
- eeroctl/commands/network/base.py +331 -0
- eeroctl/commands/network/dhcp.py +118 -0
- eeroctl/commands/network/dns.py +197 -0
- eeroctl/commands/network/forwards.py +115 -0
- eeroctl/commands/network/guest.py +162 -0
- eeroctl/commands/network/security.py +162 -0
- eeroctl/commands/network/speedtest.py +99 -0
- eeroctl/commands/network/sqm.py +194 -0
- eeroctl/commands/profile.py +671 -0
- eeroctl/commands/troubleshoot.py +317 -0
- eeroctl/context.py +254 -0
- eeroctl/errors.py +156 -0
- eeroctl/exit_codes.py +68 -0
- eeroctl/formatting/__init__.py +90 -0
- eeroctl/formatting/base.py +181 -0
- eeroctl/formatting/device.py +430 -0
- eeroctl/formatting/eero.py +591 -0
- eeroctl/formatting/misc.py +87 -0
- eeroctl/formatting/network.py +659 -0
- eeroctl/formatting/profile.py +443 -0
- eeroctl/main.py +161 -0
- eeroctl/options.py +429 -0
- eeroctl/output.py +739 -0
- eeroctl/safety.py +259 -0
- eeroctl/utils.py +181 -0
- eeroctl-1.7.1.dist-info/METADATA +115 -0
- eeroctl-1.7.1.dist-info/RECORD +45 -0
- eeroctl-1.7.1.dist-info/WHEEL +5 -0
- eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
- eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
- eeroctl-1.7.1.dist-info/top_level.txt +1 -0
eeroctl/options.py
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""Shared CLI option decorators for flexible option placement.
|
|
2
|
+
|
|
3
|
+
This module provides reusable Click option decorators that can be applied
|
|
4
|
+
to any command, allowing options like --output, --network-id, and --force
|
|
5
|
+
to be placed anywhere in the command line.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
# Options can now appear at any level:
|
|
9
|
+
eero network list --output json
|
|
10
|
+
eero --output json network list
|
|
11
|
+
eero device block "iPhone" --force
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from ..options import output_option, network_option, apply_options
|
|
15
|
+
|
|
16
|
+
@network_group.command(name="list")
|
|
17
|
+
@output_option
|
|
18
|
+
@click.pass_context
|
|
19
|
+
def network_list(ctx: click.Context, output: str | None) -> None:
|
|
20
|
+
apply_options(ctx, output=output)
|
|
21
|
+
cli_ctx = get_cli_context(ctx)
|
|
22
|
+
# cli_ctx.output_format now has the effective value
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import functools
|
|
26
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
27
|
+
|
|
28
|
+
import click
|
|
29
|
+
|
|
30
|
+
from .context import EeroCliContext, get_cli_context
|
|
31
|
+
|
|
32
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Option Value Resolution
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_effective_value(
|
|
41
|
+
ctx: click.Context,
|
|
42
|
+
local_value: Any,
|
|
43
|
+
attr_name: str,
|
|
44
|
+
default: Any = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Get the effective option value with proper precedence.
|
|
47
|
+
|
|
48
|
+
Precedence (highest to lowest):
|
|
49
|
+
1. Local value (if not None) - explicitly passed to this command
|
|
50
|
+
2. Parent context value - from EeroCliContext in parent
|
|
51
|
+
3. Default value
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
ctx: Click context
|
|
55
|
+
local_value: Value passed to the current command (None = not specified)
|
|
56
|
+
attr_name: Attribute name on EeroCliContext to check
|
|
57
|
+
default: Default value if nothing else is set
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The effective value to use
|
|
61
|
+
"""
|
|
62
|
+
# If a local value was explicitly provided, use it
|
|
63
|
+
if local_value is not None:
|
|
64
|
+
return local_value
|
|
65
|
+
|
|
66
|
+
# Walk up the context chain to find EeroCliContext
|
|
67
|
+
current: Optional[click.Context] = ctx
|
|
68
|
+
while current is not None:
|
|
69
|
+
if isinstance(current.obj, EeroCliContext):
|
|
70
|
+
parent_value = getattr(current.obj, attr_name, None)
|
|
71
|
+
if parent_value is not None:
|
|
72
|
+
return parent_value
|
|
73
|
+
current = current.parent
|
|
74
|
+
|
|
75
|
+
return default
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def apply_options(
|
|
79
|
+
ctx: click.Context,
|
|
80
|
+
*,
|
|
81
|
+
output: Optional[str] = None,
|
|
82
|
+
network_id: Optional[str] = None,
|
|
83
|
+
force: Optional[bool] = None,
|
|
84
|
+
non_interactive: Optional[bool] = None,
|
|
85
|
+
debug: Optional[bool] = None,
|
|
86
|
+
quiet: Optional[bool] = None,
|
|
87
|
+
no_color: Optional[bool] = None,
|
|
88
|
+
) -> EeroCliContext:
|
|
89
|
+
"""Apply local option values to the CLI context with proper precedence.
|
|
90
|
+
|
|
91
|
+
This function updates the EeroCliContext with effective values,
|
|
92
|
+
merging local values with inherited parent values.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
ctx: Click context
|
|
96
|
+
output: Local --output value (None = inherit from parent)
|
|
97
|
+
network_id: Local --network-id value (None = inherit from parent)
|
|
98
|
+
force: Local --force value (None = inherit from parent)
|
|
99
|
+
non_interactive: Local --non-interactive value (None = inherit from parent)
|
|
100
|
+
debug: Local --debug value (None = inherit from parent)
|
|
101
|
+
quiet: Local --quiet value (None = inherit from parent)
|
|
102
|
+
no_color: Local --no-color value (None = inherit from parent)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The updated EeroCliContext
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
@network_group.command(name="list")
|
|
109
|
+
@output_option
|
|
110
|
+
@network_option
|
|
111
|
+
@click.pass_context
|
|
112
|
+
def network_list(ctx, output, network_id):
|
|
113
|
+
cli_ctx = apply_options(ctx, output=output, network_id=network_id)
|
|
114
|
+
# Now use cli_ctx with effective values
|
|
115
|
+
"""
|
|
116
|
+
cli_ctx = get_cli_context(ctx)
|
|
117
|
+
|
|
118
|
+
# Apply output format
|
|
119
|
+
if output is not None:
|
|
120
|
+
cli_ctx.output_format = output
|
|
121
|
+
elif cli_ctx.output_format is None:
|
|
122
|
+
cli_ctx.output_format = "table"
|
|
123
|
+
|
|
124
|
+
# Apply network ID
|
|
125
|
+
if network_id is not None:
|
|
126
|
+
cli_ctx.network_id = network_id
|
|
127
|
+
|
|
128
|
+
# Apply force flag (for boolean, we need special handling)
|
|
129
|
+
if force is not None:
|
|
130
|
+
cli_ctx.force = force
|
|
131
|
+
|
|
132
|
+
# Apply non-interactive flag
|
|
133
|
+
if non_interactive is not None:
|
|
134
|
+
cli_ctx.non_interactive = non_interactive
|
|
135
|
+
|
|
136
|
+
# Apply debug flag
|
|
137
|
+
if debug is not None:
|
|
138
|
+
cli_ctx.debug = debug
|
|
139
|
+
|
|
140
|
+
# Apply quiet flag
|
|
141
|
+
if quiet is not None:
|
|
142
|
+
cli_ctx.quiet = quiet
|
|
143
|
+
|
|
144
|
+
# Apply no_color flag
|
|
145
|
+
if no_color is not None:
|
|
146
|
+
cli_ctx.no_color = no_color
|
|
147
|
+
|
|
148
|
+
# Invalidate cached renderer if format or display options changed
|
|
149
|
+
if output is not None or quiet is not None or no_color is not None:
|
|
150
|
+
cli_ctx._renderer = None
|
|
151
|
+
|
|
152
|
+
return cli_ctx
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# =============================================================================
|
|
156
|
+
# Individual Option Decorators
|
|
157
|
+
# =============================================================================
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def output_option(func: F) -> F:
|
|
161
|
+
"""Add --output/-o option to a command.
|
|
162
|
+
|
|
163
|
+
When applied, the command can accept --output anywhere in the invocation.
|
|
164
|
+
Use apply_options() or get_effective_value() to merge with parent values.
|
|
165
|
+
|
|
166
|
+
The option uses default=None to distinguish "not specified" from
|
|
167
|
+
an explicit default, enabling proper inheritance from parent context.
|
|
168
|
+
|
|
169
|
+
Example:
|
|
170
|
+
@command.command()
|
|
171
|
+
@output_option
|
|
172
|
+
@click.pass_context
|
|
173
|
+
def list_items(ctx, output):
|
|
174
|
+
cli_ctx = apply_options(ctx, output=output)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
@click.option(
|
|
178
|
+
"--output",
|
|
179
|
+
"-o",
|
|
180
|
+
type=click.Choice(["table", "list", "json", "yaml", "text"]),
|
|
181
|
+
default=None,
|
|
182
|
+
help="Output format.",
|
|
183
|
+
)
|
|
184
|
+
@functools.wraps(func)
|
|
185
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
186
|
+
return func(*args, **kwargs)
|
|
187
|
+
|
|
188
|
+
return wrapper # type: ignore[return-value]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def network_option(func: F) -> F:
|
|
192
|
+
"""Add --network-id/-n option to a command.
|
|
193
|
+
|
|
194
|
+
When applied, the command can accept --network-id anywhere in the invocation.
|
|
195
|
+
Use apply_options() or get_effective_value() to merge with parent values.
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
@command.command()
|
|
199
|
+
@network_option
|
|
200
|
+
@click.pass_context
|
|
201
|
+
def show_network(ctx, network_id):
|
|
202
|
+
cli_ctx = apply_options(ctx, network_id=network_id)
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
@click.option(
|
|
206
|
+
"--network-id",
|
|
207
|
+
"-n",
|
|
208
|
+
default=None,
|
|
209
|
+
help="Network ID to operate on.",
|
|
210
|
+
)
|
|
211
|
+
@functools.wraps(func)
|
|
212
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
213
|
+
return func(*args, **kwargs)
|
|
214
|
+
|
|
215
|
+
return wrapper # type: ignore[return-value]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def force_option(func: F) -> F:
|
|
219
|
+
"""Add --force/-y/--yes option to a command.
|
|
220
|
+
|
|
221
|
+
When applied, the command can accept --force anywhere in the invocation.
|
|
222
|
+
Use apply_options() or get_effective_value() to merge with parent values.
|
|
223
|
+
|
|
224
|
+
Note: Uses is_flag=True with flag_value/default pattern to support
|
|
225
|
+
three states: True (explicitly set), False (explicitly unset), None (inherit).
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
@command.command()
|
|
229
|
+
@force_option
|
|
230
|
+
@click.pass_context
|
|
231
|
+
def delete_item(ctx, force):
|
|
232
|
+
cli_ctx = apply_options(ctx, force=force)
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
@click.option(
|
|
236
|
+
"--force/--no-force",
|
|
237
|
+
"-y",
|
|
238
|
+
default=None,
|
|
239
|
+
help="Skip confirmation prompts.",
|
|
240
|
+
)
|
|
241
|
+
@functools.wraps(func)
|
|
242
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
243
|
+
return func(*args, **kwargs)
|
|
244
|
+
|
|
245
|
+
return wrapper # type: ignore[return-value]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def non_interactive_option(func: F) -> F:
|
|
249
|
+
"""Add --non-interactive option to a command.
|
|
250
|
+
|
|
251
|
+
When applied, the command can accept --non-interactive anywhere in the invocation.
|
|
252
|
+
In non-interactive mode, the CLI will fail if confirmation is required.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
@command.command()
|
|
256
|
+
@non_interactive_option
|
|
257
|
+
@click.pass_context
|
|
258
|
+
def dangerous_action(ctx, non_interactive):
|
|
259
|
+
cli_ctx = apply_options(ctx, non_interactive=non_interactive)
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
@click.option(
|
|
263
|
+
"--non-interactive/--interactive",
|
|
264
|
+
default=None,
|
|
265
|
+
help="Never prompt for input; fail if confirmation required.",
|
|
266
|
+
)
|
|
267
|
+
@functools.wraps(func)
|
|
268
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
269
|
+
return func(*args, **kwargs)
|
|
270
|
+
|
|
271
|
+
return wrapper # type: ignore[return-value]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def debug_option(func: F) -> F:
|
|
275
|
+
"""Add --debug/--no-debug option to a command.
|
|
276
|
+
|
|
277
|
+
When applied, the command can accept --debug anywhere in the invocation.
|
|
278
|
+
Debug mode enables verbose logging output.
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
@command.command()
|
|
282
|
+
@debug_option
|
|
283
|
+
@click.pass_context
|
|
284
|
+
def some_command(ctx, debug):
|
|
285
|
+
cli_ctx = apply_options(ctx, debug=debug)
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
@click.option(
|
|
289
|
+
"--debug/--no-debug",
|
|
290
|
+
default=None,
|
|
291
|
+
help="Enable debug logging.",
|
|
292
|
+
)
|
|
293
|
+
@functools.wraps(func)
|
|
294
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
295
|
+
return func(*args, **kwargs)
|
|
296
|
+
|
|
297
|
+
return wrapper # type: ignore[return-value]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def quiet_option(func: F) -> F:
|
|
301
|
+
"""Add --quiet/--no-quiet option to a command.
|
|
302
|
+
|
|
303
|
+
When applied, the command can accept --quiet/-q anywhere in the invocation.
|
|
304
|
+
Quiet mode suppresses non-essential output.
|
|
305
|
+
|
|
306
|
+
Example:
|
|
307
|
+
@command.command()
|
|
308
|
+
@quiet_option
|
|
309
|
+
@click.pass_context
|
|
310
|
+
def some_command(ctx, quiet):
|
|
311
|
+
cli_ctx = apply_options(ctx, quiet=quiet)
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
@click.option(
|
|
315
|
+
"--quiet/--no-quiet",
|
|
316
|
+
"-q",
|
|
317
|
+
default=None,
|
|
318
|
+
help="Suppress non-essential output.",
|
|
319
|
+
)
|
|
320
|
+
@functools.wraps(func)
|
|
321
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
322
|
+
return func(*args, **kwargs)
|
|
323
|
+
|
|
324
|
+
return wrapper # type: ignore[return-value]
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def no_color_option(func: F) -> F:
|
|
328
|
+
"""Add --no-color/--color option to a command.
|
|
329
|
+
|
|
330
|
+
When applied, the command can accept --no-color anywhere in the invocation.
|
|
331
|
+
Disables colored output for the command.
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
@command.command()
|
|
335
|
+
@no_color_option
|
|
336
|
+
@click.pass_context
|
|
337
|
+
def some_command(ctx, no_color):
|
|
338
|
+
cli_ctx = apply_options(ctx, no_color=no_color)
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
@click.option(
|
|
342
|
+
"--no-color/--color",
|
|
343
|
+
default=None,
|
|
344
|
+
help="Disable colored output.",
|
|
345
|
+
)
|
|
346
|
+
@functools.wraps(func)
|
|
347
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
348
|
+
return func(*args, **kwargs)
|
|
349
|
+
|
|
350
|
+
return wrapper # type: ignore[return-value]
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# =============================================================================
|
|
354
|
+
# Combined Option Decorators
|
|
355
|
+
# =============================================================================
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def safety_options(func: F) -> F:
|
|
359
|
+
"""Add safety-related options (--force, --non-interactive) to a command.
|
|
360
|
+
|
|
361
|
+
Combines force_option and non_interactive_option for commands that
|
|
362
|
+
perform destructive actions requiring confirmation.
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
@command.command()
|
|
366
|
+
@safety_options
|
|
367
|
+
@click.pass_context
|
|
368
|
+
def reboot_device(ctx, force, non_interactive):
|
|
369
|
+
cli_ctx = apply_options(ctx, force=force, non_interactive=non_interactive)
|
|
370
|
+
"""
|
|
371
|
+
return force_option(non_interactive_option(func))
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def common_options(func: F) -> F:
|
|
375
|
+
"""Add commonly used options (--output, --network-id) to a command.
|
|
376
|
+
|
|
377
|
+
Combines output_option and network_option for read commands that
|
|
378
|
+
display data and operate on a specific network.
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
@command.command()
|
|
382
|
+
@common_options
|
|
383
|
+
@click.pass_context
|
|
384
|
+
def list_devices(ctx, output, network_id):
|
|
385
|
+
cli_ctx = apply_options(ctx, output=output, network_id=network_id)
|
|
386
|
+
"""
|
|
387
|
+
return output_option(network_option(func))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def display_options(func: F) -> F:
|
|
391
|
+
"""Add display-related options (--debug, --quiet, --no-color) to a command.
|
|
392
|
+
|
|
393
|
+
Combines debug_option, quiet_option, and no_color_option for commands
|
|
394
|
+
where per-command display control is useful.
|
|
395
|
+
|
|
396
|
+
Example:
|
|
397
|
+
@command.command()
|
|
398
|
+
@display_options
|
|
399
|
+
@click.pass_context
|
|
400
|
+
def verbose_command(ctx, debug, quiet, no_color):
|
|
401
|
+
cli_ctx = apply_options(ctx, debug=debug, quiet=quiet, no_color=no_color)
|
|
402
|
+
"""
|
|
403
|
+
return debug_option(quiet_option(no_color_option(func)))
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def all_options(func: F) -> F:
|
|
407
|
+
"""Add all common options to a command.
|
|
408
|
+
|
|
409
|
+
Combines output_option, network_option, force_option, non_interactive_option,
|
|
410
|
+
and display options (debug, quiet, no_color).
|
|
411
|
+
Use for commands that need full control over all settings.
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
@command.command()
|
|
415
|
+
@all_options
|
|
416
|
+
@click.pass_context
|
|
417
|
+
def full_command(ctx, output, network_id, force, non_interactive, debug, quiet, no_color):
|
|
418
|
+
cli_ctx = apply_options(
|
|
419
|
+
ctx,
|
|
420
|
+
output=output,
|
|
421
|
+
network_id=network_id,
|
|
422
|
+
force=force,
|
|
423
|
+
non_interactive=non_interactive,
|
|
424
|
+
debug=debug,
|
|
425
|
+
quiet=quiet,
|
|
426
|
+
no_color=no_color,
|
|
427
|
+
)
|
|
428
|
+
"""
|
|
429
|
+
return common_options(safety_options(display_options(func)))
|