It all started with @click
and, well. It went on from there.
I'm going to post some code now.
from collections.abc import Generator
from contextlib import contextmanager, suppress
from dataclasses import dataclass
import click
import requests
from beartype.typing import Any, Sequence, TypedDict, Unpack
from src.auth import fetch_auth_token
from src.settings import SETTINGS
class CliOptions(TypedDict):
auth_type: str
chat_type: str
topic: str | None
participant: str | None
@dataclass(eq=False)
class Options:
auth_type: str
chat_type: str
topic: str | None
participant: str | None
@contextmanager
def find_in_instance(
cli_options, data
) -> Generator[list[dict[str, None | str | dict[str, str]]], Any, None]:
match cli_options:
case Options(participant=None, topic=None, chat_type=("oneOnOne" | "group" | "meeting")):
yield [item for item in data if cli_options.chat_type == item["chatType"]]
case Options(participant=None, chat_type=("oneOnOne" | "group" | "meeting")):
yield [
item
for item in data
if cli_options.chat_type == item["chatType"] and cli_options.topic in item["topic".lower()]
]
case Options(topic=None, chat_type=("oneOnOne" | "group" | "meeting")):
yield [
item
for item in data
if cli_options.chat_type == item["chatType"]
for member in item["members"]
if cli_options.participant in member["displayName"].lower()
]
case Options(chat_type=("oneOnOne" | "group" | "meeting")):
yield [
item
for item in data
if cli_options.chat_type == item["chatType"] and cli_options.topic in item["topic"]
for member in item["members"]
if cli_options.participant in member["displayName"]
]
case __:
yield [{"default": None}]
def lowercase(ctx, param, value):
if value is not None:
return value.lower()
@click.command("cli")
@click.option(
"--auth-type",
type=click.Choice(
["interactive", "code"],
case_sensitive=False,
),
default="interactive",
help="Default is 'interactive'. If running headless use 'code'",
)
@click.option(
"--chat-type",
type=click.Choice(["oneOnOne", "group", "meeting"], case_sensitive=False),
default="oneOnOne",
help="Chat is either oneOnOne [default], group or meeting.",
)
@click.option(
"--topic",
callback=lowercase,
help="Filter on chat name or topic when it contains the <string>",
)
@click.option(
"--participant",
callback=lowercase,
help="Filter on chat participants name when it contains the <string>",
)
def cli(**kwargs: Unpack[CliOptions]):
def recursive_fetch(
url: str,
headers: dict[str, str],
cli_options: Options,
index: int = 0,
) -> int:
...do some processing
..do auth and some pre prossesing
cli_options = Options(**kwargs)
recursive_fetch(url, headers, cli_options)
So, what are we looking at here? We are looking at a client that fetches chats from https://graph.microsoft.com/v1.0/me/chats?$top=50&$expand=members, recursively.
It started when ruff
complained I had too many parameters in my method call. So, naturally, I turned to **kwargs
I usually never turn to **kwargs
but with TypedDict I feel I can have a clear and concise understanding of what hides inside the dict
. So that's one thing. I haven't seen this particular pattern used with click in the wild yet. I might be the only one with this set of needs... or I'm just very poor at designing command-line applications. That might be it.
Anyways. One thing led to another. This is the first time I've used the match
statement with any success. I've tried it before but never had a use case that actually resulted in fewer lines and clearer code. Now I think I have.
I will post the auth and the Settings too - when I get around to doing it. For the time being, this post is somewhat of a placeholder so that I don't forget..
Subscribe to my newsletter
Read articles from Rune Hansén Steinnes-Westum directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by