Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python

Data classes, protocols, generics, async iteration, task groups, comprehensions, enums, and structural pattern matching.

mdBook tree-sitter
from collections.abc import AsyncIterator
from dataclasses import dataclass, field
from enum import StrEnum, auto
from typing import Protocol, TypeVar
import asyncio

T = TypeVar("T")


class State(StrEnum):
    QUEUED = auto()
    RUNNING = auto()
    DONE = auto()
    FAILED = auto()


@dataclass(slots=True)
class Job:
    name: str
    command: tuple[str, ...]
    state: State = State.QUEUED
    output: list[str] = field(default_factory=list)


class Store(Protocol[T]):
    async def save(self, key: str, value: T) -> None: ...
    async def load(self, key: str) -> T | None: ...


async def run(job: Job) -> AsyncIterator[str]:
    job.state = State.RUNNING
    proc = await asyncio.create_subprocess_exec(
        *job.command,
        stdout=asyncio.subprocess.PIPE,
    )
    assert proc.stdout is not None

    async for raw in proc.stdout:
        line = raw.decode().rstrip()
        job.output.append(line)
        yield line

    code = await proc.wait()
    job.state = State.DONE if code == 0 else State.FAILED


async def supervise(
    jobs: list[Job],
    store: Store[Job],
) -> dict[State, int]:
    async with asyncio.TaskGroup() as group:
        for job in jobs:
            group.create_task(store.save(job.name, job))

    counts = {state: 0 for state in State}
    for job in jobs:
        match job.state:
            case State.FAILED if job.output:
                print(f"{job.name}: {job.output[-1]}")
            case State.DONE:
                print(f"{job.name}: complete")
            case _:
                pass
        counts[job.state] += 1
    return counts
from collections.abc import AsyncIterator
from dataclasses import dataclass, field
from enum import StrEnum, auto
from typing import Protocol, TypeVar
import asyncio

T = TypeVar("T")


class State(StrEnum):
    QUEUED = auto()
    RUNNING = auto()
    DONE = auto()
    FAILED = auto()


@dataclass(slots=True)
class Job:
    name: str
    command: tuple[str, ...]
    state: State = State.QUEUED
    output: list[str] = field(default_factory=list)


class Store(Protocol[T]):
    async def save(self, key: str, value: T) -> None: ...
    async def load(self, key: str) -> T | None: ...


async def run(job: Job) -> AsyncIterator[str]:
    job.state = State.RUNNING
    proc = await asyncio.create_subprocess_exec(
        *job.command,
        stdout=asyncio.subprocess.PIPE,
    )
    assert proc.stdout is not None

    async for raw in proc.stdout:
        line = raw.decode().rstrip()
        job.output.append(line)
        yield line

    code = await proc.wait()
    job.state = State.DONE if code == 0 else State.FAILED


async def supervise(
    jobs: list[Job],
    store: Store[Job],
) -> dict[State, int]:
    async with asyncio.TaskGroup() as group:
        for job in jobs:
            group.create_task(store.save(job.name, job))

    counts = {state: 0 for state in State}
    for job in jobs:
        match job.state:
            case State.FAILED if job.output:
                print(f"{job.name}: {job.output[-1]}")
            case State.DONE:
                print(f"{job.name}: complete")
            case _:
                pass
        counts[job.state] += 1
    return counts