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.
Files changed (45) hide show
  1. eeroctl/__init__.py +19 -0
  2. eeroctl/commands/__init__.py +32 -0
  3. eeroctl/commands/activity.py +237 -0
  4. eeroctl/commands/auth.py +471 -0
  5. eeroctl/commands/completion.py +142 -0
  6. eeroctl/commands/device.py +492 -0
  7. eeroctl/commands/eero/__init__.py +12 -0
  8. eeroctl/commands/eero/base.py +224 -0
  9. eeroctl/commands/eero/led.py +154 -0
  10. eeroctl/commands/eero/nightlight.py +235 -0
  11. eeroctl/commands/eero/updates.py +82 -0
  12. eeroctl/commands/network/__init__.py +18 -0
  13. eeroctl/commands/network/advanced.py +191 -0
  14. eeroctl/commands/network/backup.py +162 -0
  15. eeroctl/commands/network/base.py +331 -0
  16. eeroctl/commands/network/dhcp.py +118 -0
  17. eeroctl/commands/network/dns.py +197 -0
  18. eeroctl/commands/network/forwards.py +115 -0
  19. eeroctl/commands/network/guest.py +162 -0
  20. eeroctl/commands/network/security.py +162 -0
  21. eeroctl/commands/network/speedtest.py +99 -0
  22. eeroctl/commands/network/sqm.py +194 -0
  23. eeroctl/commands/profile.py +671 -0
  24. eeroctl/commands/troubleshoot.py +317 -0
  25. eeroctl/context.py +254 -0
  26. eeroctl/errors.py +156 -0
  27. eeroctl/exit_codes.py +68 -0
  28. eeroctl/formatting/__init__.py +90 -0
  29. eeroctl/formatting/base.py +181 -0
  30. eeroctl/formatting/device.py +430 -0
  31. eeroctl/formatting/eero.py +591 -0
  32. eeroctl/formatting/misc.py +87 -0
  33. eeroctl/formatting/network.py +659 -0
  34. eeroctl/formatting/profile.py +443 -0
  35. eeroctl/main.py +161 -0
  36. eeroctl/options.py +429 -0
  37. eeroctl/output.py +739 -0
  38. eeroctl/safety.py +259 -0
  39. eeroctl/utils.py +181 -0
  40. eeroctl-1.7.1.dist-info/METADATA +115 -0
  41. eeroctl-1.7.1.dist-info/RECORD +45 -0
  42. eeroctl-1.7.1.dist-info/WHEEL +5 -0
  43. eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
  44. eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
  45. 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)))