pyalexatodo.cli
1import asyncio 2import json 3import sys 4from functools import wraps 5from pathlib import Path 6from typing import Any, cast 7 8try: 9 import keyring 10 import orjson 11 import typer 12 from rich.console import Console 13except ImportError: 14 print("Required packages for CLI usage are not installed. Please install pyalexatodo[cli] to install them. " 15 "For example with pip: pip install \"pyalexatodo[cli]\"") 16 sys.exit(1) 17 18from aioamazondevices import CannotAuthenticate, CannotConnect 19from aioamazondevices.api import AmazonEchoApi 20from aioamazondevices.exceptions import AmazonError, CannotRegisterDevice 21from aiohttp import ClientSession 22 23from pyalexatodo.api import AlexaToDoAPI 24from pyalexatodo.exceptions import ItemNotFoundException 25from pyalexatodo.models.cli_settings import CliSettings 26 27app = typer.Typer() 28console = Console() 29 30# Global variables to hold the API client and default list ID after initialization 31alexa_list_api: AlexaToDoAPI 32client_session: ClientSession 33default_list_id: str = "" 34 35KEYRING_SERVICE = "alexalists-cli" 36PASSWORD_KEY = "amazon-password" 37 38### Helper functions for file I/O and API initialization ### 39 40def read_from_file(data_file: str) -> dict[str, Any]: 41 """Load stored login data from file.""" 42 if not data_file or not Path(data_file).exists(): 43 print( 44 "Cannot find previous login data file: ", 45 data_file, 46 ) 47 return {} 48 49 with open(Path(data_file), "r") as f: 50 return cast("dict[str, Any]", json.loads(f.read())) 51 52 53def save_to_file( 54 raw_data: str | dict[str, Any], 55 content_type: str = "application/json", 56) -> None: 57 """Save login_data data to disk.""" 58 if not raw_data: 59 return 60 61 try: 62 fullpath = Path(get_outputpath("login_data.json")) 63 64 # Create main output directory and timestamp subdirectory 65 output_dir = fullpath.parent 66 output_dir.mkdir(parents=True, exist_ok=True) 67 68 # Convert dict to JSON string if needed 69 if isinstance(raw_data, dict): 70 json_data = raw_data 71 else: 72 # Assume it's a JSON string 73 json_data = orjson.loads(raw_data) 74 75 data = orjson.dumps( 76 json_data, 77 option=orjson.OPT_INDENT_2, 78 ).decode("utf-8") 79 80 print(f"Saving data to {fullpath}") 81 82 with open(fullpath, "w", encoding="utf-8") as file: 83 file.write(data) 84 file.write("\n") 85 except Exception as e: 86 print(f"Error saving login data: {e}") 87 88def get_outputpath(filename: str) -> str: 89 """Get the absolute path for storing application files. 90 91 Args:t 92 filename (str): The name of the file. 93 94 Returns: 95 str: The absolute path to the file in the user's home directory. 96 """ 97 return Path(Path.home(), ".pyalexatodo", filename).as_posix() 98 99 100async def init_api(): 101 """Initialize the Alexa API using stored credentials and settings. 102 103 This function: 104 1. Loads the CLI settings from the config file 105 2. Retrieves credentials from the system keyring 106 3. Initializes and tests the Alexa login 107 4. Creates the API instance 108 109 Returns: 110 AlexaListAPI: An initialized API instance. 111 112 Raises: 113 FileNotFoundError: If the CLI settings file is not found. 114 SystemExit: If login fails or settings are invalid. 115 """ 116 global alexa_list_api, default_list_id, client_session 117 118 try: 119 # Load CLI settings 120 with open(get_outputpath("cli_settings.json"), "r") as f: 121 settings = CliSettings.model_validate_json(f.read()) 122 123 login_data_stored = read_from_file(get_outputpath("login_data.json")) 124 125 client_session = ClientSession() 126 127 password = keyring.get_password( 128 KEYRING_SERVICE, f"{settings.email}-{PASSWORD_KEY}" 129 ) 130 131 if not password: 132 console.print( 133 f"[bold red]No password found in keyring for {settings.email}. Please run setup option first.[/bold red]" 134 ) 135 sys.exit(1) 136 137 amazon_echo_api = AmazonEchoApi( 138 client_session=client_session, 139 login_email=settings.email, 140 login_password=password, 141 login_data=login_data_stored, 142 ) 143 144 try: 145 await amazon_echo_api.login.login_mode_stored_data() 146 except CannotAuthenticate: 147 console.print( 148 f"[bold red]Cannot authenticate with {settings.email} credentials[/bold red]" 149 ) 150 raise 151 except CannotConnect: 152 console.print( 153 f"[bold red]Cannot connect to {amazon_echo_api.domain} Amazon host[/bold red]" 154 ) 155 raise 156 except CannotRegisterDevice: 157 console.print( 158 f"[bold red]Cannot register device for {settings.email}[/bold red]" 159 ) 160 raise 161 162 alexa_list_api = AlexaToDoAPI(amazon_echo_api) 163 default_list_id = settings.default_list_id 164 165 except AmazonError: 166 console.print("[bold red]Login failed.[/bold red]") 167 sys.exit(1) 168 except FileNotFoundError: 169 console.print( 170 "CLI settings not found. Please run setup option first.", style="red" 171 ) 172 sys.exit(1) 173 174### Decorators for CLI commands ### 175 176def with_alexa_api(func): 177 """Decorator that initializes the Alexa API connection before function execution. 178 179 Args: 180 func: The async function to wrap. 181 182 Returns: 183 wrapper: The wrapped function that handles API initialization and cleanup. 184 185 Example: 186 @with_alexa_api 187 async def my_function(): 188 # Function will have access to initialized alexa_list_api 189 pass 190 """ 191 192 @wraps(func) 193 async def wrapper(*args, **kwargs): 194 try: 195 with console.status("Logging into Alexa API..."): 196 await init_api() 197 return await func(*args, **kwargs) 198 finally: 199 if client_session: 200 await client_session.close() 201 202 return wrapper 203 204 205def cli_command(func): 206 """Decorator that wraps an async function to run in the asyncio event loop. 207 208 Args: 209 func: The async function to wrap. 210 211 Returns: 212 wrapper: The wrapped function that handles asyncio.run. 213 214 Example: 215 @cli_command 216 async def my_function(): 217 # Function will run in asyncio event loop 218 pass 219 """ 220 221 @wraps(func) 222 def wrapper(*args, **kwargs): 223 return asyncio.run(func(*args, **kwargs)) 224 225 return wrapper 226 227### CLI Commands ### 228 229@app.command() 230def setup(): 231 """Command to set up the Alexa Lists CLI with user credentials and preferences.""" 232 asyncio.run(setup_async()) 233 234 235async def setup_async(): 236 """Async implementation of the setup command. 237 238 Guides the user through the setup process: 239 1. Collects Amazon credentials and OTP secret 240 2. Stores sensitive data in system keyring 241 3. Authenticates with Amazon 242 4. Lets user select default list 243 5. Saves non-sensitive settings to file 244 245 Raises: 246 Exception: If any step of the setup process fails 247 """ 248 try: 249 # Welcome message 250 console.print("[bold blue]Welcome to the Alexa Lists CLI Setup![/bold blue]") 251 console.print("This will guide you through setting up your Alexa Lists CLI.") 252 console.print( 253 "You will need your Amazon account credentials and an OTP token for two-factor authentication.\n" 254 ) 255 console.print( 256 "The password will be stored securely in your system's keyring.\n" 257 ) 258 259 while True: 260 email = console.input("Enter your Amazon email: ").strip() 261 if email and "@" in email and "." in email: 262 break 263 console.print( 264 "[red]Invalid email format. Please enter a valid email address.[/red]" 265 ) 266 267 while True: 268 password = console.input("Enter your Amazon password: ", password=True) 269 if password: # Basic password length check 270 break 271 console.print("[red]Password cannot be empty.[/red]") 272 273 # Store sensitive data in keyring 274 keyring.set_password(KEYRING_SERVICE, f"{email}-{PASSWORD_KEY}", password) 275 276 client_session = ClientSession() 277 278 amazon_echo_api = AmazonEchoApi( 279 client_session=client_session, login_email=email, login_password=password 280 ) 281 282 while True: 283 otp_token = console.input("Enter current OTP token: ") 284 if len(otp_token) == 6: # Basic OTP token length check 285 break 286 console.print("[red]OTP token must be 6 digits.[/red]") 287 288 with console.status("[bold blue]Logging into Alexa API..."): 289 try: 290 login_data = await amazon_echo_api.login.login_mode_interactive( 291 otp_token 292 ) 293 except CannotAuthenticate: 294 console.print( 295 f"[bold red]Cannot authenticate with {email} credentials[/bold red]" 296 ) 297 raise 298 except CannotConnect: 299 console.print( 300 f"[bold red]Cannot connect to {amazon_echo_api.domain} Amazon host[/bold red]" 301 ) 302 raise 303 except CannotRegisterDevice: 304 console.print( 305 f"[bold red]Cannot register device for {email}[/bold red]" 306 ) 307 raise 308 309 with console.status("[bold blue]Saving login data to disk..."): 310 save_to_file(login_data) 311 312 console.print("[green]Logged in successfully![/green]") 313 314 # Get available lists 315 alexa_list_api = AlexaToDoAPI(amazon_echo_api) 316 with console.status("[bold blue]Fetching available lists..."): 317 lists = await alexa_list_api.get_lists() 318 319 # Display lists and get user selection 320 console.print("\n[bold]Available Lists:[/bold]") 321 for i, list_info in enumerate(lists): 322 console.print(f" [{i}] {list_info.name}") 323 324 while True: 325 default_list_id = console.input( 326 "\nWhich is your default list? Enter the number: " 327 ) 328 if default_list_id.isdigit() and 0 <= int(default_list_id) < len(lists): 329 default_list_id = lists[int(default_list_id)].id 330 break 331 console.print("[red]Invalid input. Please enter a valid list number.[/red]") 332 333 # Save non-sensitive settings 334 cli_settings = CliSettings( 335 email=email, 336 default_list_id=default_list_id, 337 ) 338 339 settings_path = get_outputpath("cli_settings.json") 340 cli_settings_json = cli_settings.model_dump_json(indent=4) 341 with open(settings_path, "w") as f: 342 f.write(cli_settings_json) 343 344 console.print("[green]Settings and credentials saved successfully![/green]") 345 346 except AmazonError: 347 console.print("[bold red]Login failed.[/bold red]") 348 sys.exit(1) 349 except Exception: 350 console.print("[bold red]Error during setup:[/bold red]") 351 raise # Typer will catch this and print the stack trace for debugging 352 finally: 353 if "alexa_login" in locals(): 354 await client_session.close() 355 356 357@app.command() 358@cli_command 359@with_alexa_api 360async def list(list_id: str = ""): 361 """Fetch and display all items from a specified Alexa list. 362 363 Args: 364 list_id: The ID of the list to fetch items from. 365 If not provided, uses the default list. 366 """ 367 if not list_id: 368 list_id = default_list_id 369 370 with console.status("Fetching list items from Alexa API..."): 371 list_items = await alexa_list_api.get_list_items(list_id) 372 373 for list_item in list_items: 374 line = typer.style( 375 f"[{'x' if list_item.is_checked else ' '}] {list_item.name}", 376 fg=typer.colors.GREEN if list_item.is_checked else typer.colors.RED, 377 ) 378 typer.echo(line) 379 380 381@app.command() 382@cli_command 383@with_alexa_api 384async def check(item_name: str, list_id: str = ""): 385 """Toggle the checked status of an item in a specified Alexa list. 386 387 Args: 388 item_name: The name of the item to toggle. 389 list_id: The ID of the list containing the item. 390 If not provided, uses the default list. 391 392 Raises: 393 ItemNotFoundException: If the item is not found in the list. 394 """ 395 if not list_id: 396 list_id = default_list_id 397 398 try: 399 with console.status("Fetching item from Alexa API and find item by name..."): 400 item = await alexa_list_api.get_item_by_name(list_id, item_name) 401 402 if item is None: 403 console.print(f'Item "{item_name}" not found.', style="red") 404 return 405 406 with console.status("Toggling item status..."): 407 await alexa_list_api.set_item_checked_status( 408 list_id, item.id, not item.is_checked, item.version 409 ) 410 411 console.print(f'Item "{item_name}" toggled sucessfully.', style="green") 412 except ItemNotFoundException: 413 console.print(f'Item "{item_name}" not found.', style="red") 414 415 416@app.command() 417@cli_command 418@with_alexa_api 419async def add(item_name: str, list_id: str = ""): 420 """Add a new item to a specified Alexa list. 421 422 Args: 423 item_name: The name of the item to add. 424 list_id: The ID of the list to add the item to. 425 If not provided, uses the default list. 426 """ 427 if not list_id: 428 list_id = default_list_id 429 430 with console.status("Adding item to list..."): 431 await alexa_list_api.add_item(list_id, item_name) 432 433 console.print(f'Item "{item_name}" added successfully.', style="green") 434 435 436@app.command() 437@cli_command 438@with_alexa_api 439async def remove(item_name: str, list_id: str = ""): 440 """Remove an item from a specified Alexa list. 441 442 Args: 443 item_name: The name of the item to remove. 444 list_id: The ID of the list to remove the item from. 445 If not provided, uses the default list. 446 447 Raises: 448 ItemNotFoundException: If the item is not found in the list. 449 """ 450 if not list_id: 451 list_id = default_list_id 452 453 try: 454 with console.status("Fetching item from Alexa API and find item by name..."): 455 item = await alexa_list_api.get_item_by_name(list_id, item_name) 456 457 with console.status("Removing item from list..."): 458 await alexa_list_api.delete_item(list_id, item.id, item.version) 459 460 console.print(f'Item "{item_name}" removed successfully.', style="green") 461 except ItemNotFoundException: 462 console.print(f'Item "{item_name}" not found.', style="red") 463 464@app.command() 465@cli_command 466@with_alexa_api 467async def lists(): 468 """Fetch and display all available Alexa lists.""" 469 with console.status("Fetching available lists from Alexa API..."): 470 lists = await alexa_list_api.get_lists() 471 472 console.print("\n[bold]Available Lists:[/bold]") 473 for list_info in lists: 474 console.print(f" - {list_info.name} (ID: {list_info.id})") 475 476if __name__ == "__main__": 477 app()
41def read_from_file(data_file: str) -> dict[str, Any]: 42 """Load stored login data from file.""" 43 if not data_file or not Path(data_file).exists(): 44 print( 45 "Cannot find previous login data file: ", 46 data_file, 47 ) 48 return {} 49 50 with open(Path(data_file), "r") as f: 51 return cast("dict[str, Any]", json.loads(f.read()))
Load stored login data from file.
54def save_to_file( 55 raw_data: str | dict[str, Any], 56 content_type: str = "application/json", 57) -> None: 58 """Save login_data data to disk.""" 59 if not raw_data: 60 return 61 62 try: 63 fullpath = Path(get_outputpath("login_data.json")) 64 65 # Create main output directory and timestamp subdirectory 66 output_dir = fullpath.parent 67 output_dir.mkdir(parents=True, exist_ok=True) 68 69 # Convert dict to JSON string if needed 70 if isinstance(raw_data, dict): 71 json_data = raw_data 72 else: 73 # Assume it's a JSON string 74 json_data = orjson.loads(raw_data) 75 76 data = orjson.dumps( 77 json_data, 78 option=orjson.OPT_INDENT_2, 79 ).decode("utf-8") 80 81 print(f"Saving data to {fullpath}") 82 83 with open(fullpath, "w", encoding="utf-8") as file: 84 file.write(data) 85 file.write("\n") 86 except Exception as e: 87 print(f"Error saving login data: {e}")
Save login_data data to disk.
89def get_outputpath(filename: str) -> str: 90 """Get the absolute path for storing application files. 91 92 Args:t 93 filename (str): The name of the file. 94 95 Returns: 96 str: The absolute path to the file in the user's home directory. 97 """ 98 return Path(Path.home(), ".pyalexatodo", filename).as_posix()
Get the absolute path for storing application files.
Args:t filename (str): The name of the file.
Returns:
str: The absolute path to the file in the user's home directory.
101async def init_api(): 102 """Initialize the Alexa API using stored credentials and settings. 103 104 This function: 105 1. Loads the CLI settings from the config file 106 2. Retrieves credentials from the system keyring 107 3. Initializes and tests the Alexa login 108 4. Creates the API instance 109 110 Returns: 111 AlexaListAPI: An initialized API instance. 112 113 Raises: 114 FileNotFoundError: If the CLI settings file is not found. 115 SystemExit: If login fails or settings are invalid. 116 """ 117 global alexa_list_api, default_list_id, client_session 118 119 try: 120 # Load CLI settings 121 with open(get_outputpath("cli_settings.json"), "r") as f: 122 settings = CliSettings.model_validate_json(f.read()) 123 124 login_data_stored = read_from_file(get_outputpath("login_data.json")) 125 126 client_session = ClientSession() 127 128 password = keyring.get_password( 129 KEYRING_SERVICE, f"{settings.email}-{PASSWORD_KEY}" 130 ) 131 132 if not password: 133 console.print( 134 f"[bold red]No password found in keyring for {settings.email}. Please run setup option first.[/bold red]" 135 ) 136 sys.exit(1) 137 138 amazon_echo_api = AmazonEchoApi( 139 client_session=client_session, 140 login_email=settings.email, 141 login_password=password, 142 login_data=login_data_stored, 143 ) 144 145 try: 146 await amazon_echo_api.login.login_mode_stored_data() 147 except CannotAuthenticate: 148 console.print( 149 f"[bold red]Cannot authenticate with {settings.email} credentials[/bold red]" 150 ) 151 raise 152 except CannotConnect: 153 console.print( 154 f"[bold red]Cannot connect to {amazon_echo_api.domain} Amazon host[/bold red]" 155 ) 156 raise 157 except CannotRegisterDevice: 158 console.print( 159 f"[bold red]Cannot register device for {settings.email}[/bold red]" 160 ) 161 raise 162 163 alexa_list_api = AlexaToDoAPI(amazon_echo_api) 164 default_list_id = settings.default_list_id 165 166 except AmazonError: 167 console.print("[bold red]Login failed.[/bold red]") 168 sys.exit(1) 169 except FileNotFoundError: 170 console.print( 171 "CLI settings not found. Please run setup option first.", style="red" 172 ) 173 sys.exit(1)
Initialize the Alexa API using stored credentials and settings.
This function:
- Loads the CLI settings from the config file
- Retrieves credentials from the system keyring
- Initializes and tests the Alexa login
- Creates the API instance
Returns:
AlexaListAPI: An initialized API instance.
Raises:
- FileNotFoundError: If the CLI settings file is not found.
- SystemExit: If login fails or settings are invalid.
177def with_alexa_api(func): 178 """Decorator that initializes the Alexa API connection before function execution. 179 180 Args: 181 func: The async function to wrap. 182 183 Returns: 184 wrapper: The wrapped function that handles API initialization and cleanup. 185 186 Example: 187 @with_alexa_api 188 async def my_function(): 189 # Function will have access to initialized alexa_list_api 190 pass 191 """ 192 193 @wraps(func) 194 async def wrapper(*args, **kwargs): 195 try: 196 with console.status("Logging into Alexa API..."): 197 await init_api() 198 return await func(*args, **kwargs) 199 finally: 200 if client_session: 201 await client_session.close() 202 203 return wrapper
Decorator that initializes the Alexa API connection before function execution.
Arguments:
- func: The async function to wrap.
Returns:
wrapper: The wrapped function that handles API initialization and cleanup.
Example:
@with_alexa_api async def my_function(): # Function will have access to initialized alexa_list_api pass
206def cli_command(func): 207 """Decorator that wraps an async function to run in the asyncio event loop. 208 209 Args: 210 func: The async function to wrap. 211 212 Returns: 213 wrapper: The wrapped function that handles asyncio.run. 214 215 Example: 216 @cli_command 217 async def my_function(): 218 # Function will run in asyncio event loop 219 pass 220 """ 221 222 @wraps(func) 223 def wrapper(*args, **kwargs): 224 return asyncio.run(func(*args, **kwargs)) 225 226 return wrapper
Decorator that wraps an async function to run in the asyncio event loop.
Arguments:
- func: The async function to wrap.
Returns:
wrapper: The wrapped function that handles asyncio.run.
Example:
@cli_command async def my_function(): # Function will run in asyncio event loop pass
230@app.command() 231def setup(): 232 """Command to set up the Alexa Lists CLI with user credentials and preferences.""" 233 asyncio.run(setup_async())
Command to set up the Alexa Lists CLI with user credentials and preferences.
236async def setup_async(): 237 """Async implementation of the setup command. 238 239 Guides the user through the setup process: 240 1. Collects Amazon credentials and OTP secret 241 2. Stores sensitive data in system keyring 242 3. Authenticates with Amazon 243 4. Lets user select default list 244 5. Saves non-sensitive settings to file 245 246 Raises: 247 Exception: If any step of the setup process fails 248 """ 249 try: 250 # Welcome message 251 console.print("[bold blue]Welcome to the Alexa Lists CLI Setup![/bold blue]") 252 console.print("This will guide you through setting up your Alexa Lists CLI.") 253 console.print( 254 "You will need your Amazon account credentials and an OTP token for two-factor authentication.\n" 255 ) 256 console.print( 257 "The password will be stored securely in your system's keyring.\n" 258 ) 259 260 while True: 261 email = console.input("Enter your Amazon email: ").strip() 262 if email and "@" in email and "." in email: 263 break 264 console.print( 265 "[red]Invalid email format. Please enter a valid email address.[/red]" 266 ) 267 268 while True: 269 password = console.input("Enter your Amazon password: ", password=True) 270 if password: # Basic password length check 271 break 272 console.print("[red]Password cannot be empty.[/red]") 273 274 # Store sensitive data in keyring 275 keyring.set_password(KEYRING_SERVICE, f"{email}-{PASSWORD_KEY}", password) 276 277 client_session = ClientSession() 278 279 amazon_echo_api = AmazonEchoApi( 280 client_session=client_session, login_email=email, login_password=password 281 ) 282 283 while True: 284 otp_token = console.input("Enter current OTP token: ") 285 if len(otp_token) == 6: # Basic OTP token length check 286 break 287 console.print("[red]OTP token must be 6 digits.[/red]") 288 289 with console.status("[bold blue]Logging into Alexa API..."): 290 try: 291 login_data = await amazon_echo_api.login.login_mode_interactive( 292 otp_token 293 ) 294 except CannotAuthenticate: 295 console.print( 296 f"[bold red]Cannot authenticate with {email} credentials[/bold red]" 297 ) 298 raise 299 except CannotConnect: 300 console.print( 301 f"[bold red]Cannot connect to {amazon_echo_api.domain} Amazon host[/bold red]" 302 ) 303 raise 304 except CannotRegisterDevice: 305 console.print( 306 f"[bold red]Cannot register device for {email}[/bold red]" 307 ) 308 raise 309 310 with console.status("[bold blue]Saving login data to disk..."): 311 save_to_file(login_data) 312 313 console.print("[green]Logged in successfully![/green]") 314 315 # Get available lists 316 alexa_list_api = AlexaToDoAPI(amazon_echo_api) 317 with console.status("[bold blue]Fetching available lists..."): 318 lists = await alexa_list_api.get_lists() 319 320 # Display lists and get user selection 321 console.print("\n[bold]Available Lists:[/bold]") 322 for i, list_info in enumerate(lists): 323 console.print(f" [{i}] {list_info.name}") 324 325 while True: 326 default_list_id = console.input( 327 "\nWhich is your default list? Enter the number: " 328 ) 329 if default_list_id.isdigit() and 0 <= int(default_list_id) < len(lists): 330 default_list_id = lists[int(default_list_id)].id 331 break 332 console.print("[red]Invalid input. Please enter a valid list number.[/red]") 333 334 # Save non-sensitive settings 335 cli_settings = CliSettings( 336 email=email, 337 default_list_id=default_list_id, 338 ) 339 340 settings_path = get_outputpath("cli_settings.json") 341 cli_settings_json = cli_settings.model_dump_json(indent=4) 342 with open(settings_path, "w") as f: 343 f.write(cli_settings_json) 344 345 console.print("[green]Settings and credentials saved successfully![/green]") 346 347 except AmazonError: 348 console.print("[bold red]Login failed.[/bold red]") 349 sys.exit(1) 350 except Exception: 351 console.print("[bold red]Error during setup:[/bold red]") 352 raise # Typer will catch this and print the stack trace for debugging 353 finally: 354 if "alexa_login" in locals(): 355 await client_session.close()
Async implementation of the setup command.
Guides the user through the setup process:
- Collects Amazon credentials and OTP secret
- Stores sensitive data in system keyring
- Authenticates with Amazon
- Lets user select default list
- Saves non-sensitive settings to file
Raises:
- Exception: If any step of the setup process fails
358@app.command() 359@cli_command 360@with_alexa_api 361async def list(list_id: str = ""): 362 """Fetch and display all items from a specified Alexa list. 363 364 Args: 365 list_id: The ID of the list to fetch items from. 366 If not provided, uses the default list. 367 """ 368 if not list_id: 369 list_id = default_list_id 370 371 with console.status("Fetching list items from Alexa API..."): 372 list_items = await alexa_list_api.get_list_items(list_id) 373 374 for list_item in list_items: 375 line = typer.style( 376 f"[{'x' if list_item.is_checked else ' '}] {list_item.name}", 377 fg=typer.colors.GREEN if list_item.is_checked else typer.colors.RED, 378 ) 379 typer.echo(line)
Fetch and display all items from a specified Alexa list.
Arguments:
- list_id: The ID of the list to fetch items from. If not provided, uses the default list.
382@app.command() 383@cli_command 384@with_alexa_api 385async def check(item_name: str, list_id: str = ""): 386 """Toggle the checked status of an item in a specified Alexa list. 387 388 Args: 389 item_name: The name of the item to toggle. 390 list_id: The ID of the list containing the item. 391 If not provided, uses the default list. 392 393 Raises: 394 ItemNotFoundException: If the item is not found in the list. 395 """ 396 if not list_id: 397 list_id = default_list_id 398 399 try: 400 with console.status("Fetching item from Alexa API and find item by name..."): 401 item = await alexa_list_api.get_item_by_name(list_id, item_name) 402 403 if item is None: 404 console.print(f'Item "{item_name}" not found.', style="red") 405 return 406 407 with console.status("Toggling item status..."): 408 await alexa_list_api.set_item_checked_status( 409 list_id, item.id, not item.is_checked, item.version 410 ) 411 412 console.print(f'Item "{item_name}" toggled sucessfully.', style="green") 413 except ItemNotFoundException: 414 console.print(f'Item "{item_name}" not found.', style="red")
Toggle the checked status of an item in a specified Alexa list.
Arguments:
- item_name: The name of the item to toggle.
- list_id: The ID of the list containing the item. If not provided, uses the default list.
Raises:
- ItemNotFoundException: If the item is not found in the list.
417@app.command() 418@cli_command 419@with_alexa_api 420async def add(item_name: str, list_id: str = ""): 421 """Add a new item to a specified Alexa list. 422 423 Args: 424 item_name: The name of the item to add. 425 list_id: The ID of the list to add the item to. 426 If not provided, uses the default list. 427 """ 428 if not list_id: 429 list_id = default_list_id 430 431 with console.status("Adding item to list..."): 432 await alexa_list_api.add_item(list_id, item_name) 433 434 console.print(f'Item "{item_name}" added successfully.', style="green")
Add a new item to a specified Alexa list.
Arguments:
- item_name: The name of the item to add.
- list_id: The ID of the list to add the item to. If not provided, uses the default list.
437@app.command() 438@cli_command 439@with_alexa_api 440async def remove(item_name: str, list_id: str = ""): 441 """Remove an item from a specified Alexa list. 442 443 Args: 444 item_name: The name of the item to remove. 445 list_id: The ID of the list to remove the item from. 446 If not provided, uses the default list. 447 448 Raises: 449 ItemNotFoundException: If the item is not found in the list. 450 """ 451 if not list_id: 452 list_id = default_list_id 453 454 try: 455 with console.status("Fetching item from Alexa API and find item by name..."): 456 item = await alexa_list_api.get_item_by_name(list_id, item_name) 457 458 with console.status("Removing item from list..."): 459 await alexa_list_api.delete_item(list_id, item.id, item.version) 460 461 console.print(f'Item "{item_name}" removed successfully.', style="green") 462 except ItemNotFoundException: 463 console.print(f'Item "{item_name}" not found.', style="red")
Remove an item from a specified Alexa list.
Arguments:
- item_name: The name of the item to remove.
- list_id: The ID of the list to remove the item from. If not provided, uses the default list.
Raises:
- ItemNotFoundException: If the item is not found in the list.
465@app.command() 466@cli_command 467@with_alexa_api 468async def lists(): 469 """Fetch and display all available Alexa lists.""" 470 with console.status("Fetching available lists from Alexa API..."): 471 lists = await alexa_list_api.get_lists() 472 473 console.print("\n[bold]Available Lists:[/bold]") 474 for list_info in lists: 475 console.print(f" - {list_info.name} (ID: {list_info.id})")
Fetch and display all available Alexa lists.