Skip to content

Latest commit

 

History

History
196 lines (166 loc) · 9.44 KB

File metadata and controls

196 lines (166 loc) · 9.44 KB

API Reference

rotapool.Pool[T]

pool = Pool(
    resources: list[Resource[T]] | dict[str, Resource[T]],
    # resources:       A list of Resource objects, or a dict mapping resource_id -> Resource.
    #                  Duplicate resource_ids in list form raise ValueError, as does a
    #                  dict key that does not match its Resource's resource_id.

    max_attempts: int = 3,
    # max_attempts:    Total retry budget per run() call. Each attempt picks among
    #                  currently eligible resources -- one that triggered cooldown or
    #                  disable stays ineligible while that state lasts (a zero-second
    #                  cooldown can make it eligible again immediately). Effectively
    #                  capped at len(resources); raises PoolExhausted once spent.

    cooldown_table: tuple[float, ...] = (30.0, 120.0, 300.0, 600.0),
    # cooldown_table:  Escalation table indexed by consecutive_cooldown count.
    #                  1st cooldown -> cooldown_table[0], 2nd -> cooldown_table[1], etc.
    #                  Out-of-range values clamp to the last entry.
    #                  Entries must be finite and >= 0.

    strategy: Literal["round_robin", "primary_backup"] = "round_robin",
    # strategy:        Selection policy among eligible resources. "round_robin"
    #                  (default) balances by fewest in-flight, then oldest
    #                  last_acquired_at. "primary_backup" returns the first eligible
    #                  resource in list/dict order (ordering is the priority ranking).
    #                  Pool-level by design; not overridable per call. See "Selection".
)
await pool.run(
    operation: Callable[[Resource[T]], Awaitable[R]],
    # operation:       Callable receiving the selected Resource and returning an
    #                  Awaitable. Raise CooldownResource or DisableResource to
    #                  signal resource health. Any other exception is treated as
    #                  "resource is fine" and propagates to the caller.
    #                  Accepted return types:
    #                    - coroutine          (typical async def)        -- cancellable
    #                    - asyncio.Future     (e.g. loop.create_future)  -- cancellable
    #                    - any Awaitable      (custom __await__)         -- best-effort
    #                  Returning a non-Awaitable raises TypeError at call time.

    *,                 # All following parameters are keyword-only.

    max_attempts: int | None = None,
    # max_attempts:    Per-call override of the pool's max_attempts. None = use pool default.

    deadline: float | None = None,
    # deadline:        Absolute time.monotonic() value that gates when each attempt may
    #                  start and caps the inter-attempt pause. Does not interrupt an
    #                  in-flight operation, so a single long call can overrun it. Raises
    #                  PoolExhausted when a new attempt would start past it. None = none.

    retry_delay: float = 0.5,
    # retry_delay:     Base pause (seconds) between failed attempts. Must be >= 0.
    #                  The actual pause is jittered to retry_delay * uniform(0.5, 1.5)
    #                  (mean stays retry_delay) so concurrent callers do not retry in
    #                  lockstep and stampede the next eligible resource.

    wait_for_cooldown: bool = False,
    # wait_for_cooldown: When no resource is eligible at the start of an attempt,
    #                  sleep until the earliest cooldown_until among cooling resources
    #                  and select again, instead of raising PoolExhausted immediately.
    #                  Never waits on disabled or saturated resources (no known wake-up
    #                  time); raises immediately when the earliest cooldown ends at or
    #                  after the deadline. The wake-up is jittered by an extra
    #                  retry_delay * uniform(0, 1), capped by the deadline, to avoid
    #                  waiter stampedes at the expiry instant. False = fail fast (default).

    request_id: str | None = None,
    # request_id:      Opaque string attached to every Usage created by this call.
    #                  Auto-generated UUID when None.
) -> R
@pool.use(
    max_attempts: int | None = None,     # Per-call override; None = use pool default
    deadline: float | None = None,       # Absolute time.monotonic() deadline
    retry_delay: float = 0.5,            # Base pause between failed attempts (jittered +/-50%)
    wait_for_cooldown: bool = False,     # Wait out the earliest cooldown instead of failing fast
)
# Returns a decorator. The decorated function receives a Resource[T] as its
# first positional argument (injected by the wrapper), followed by caller args.
# Any callable returning an Awaitable is accepted (async def, sync function
# returning a coroutine / Future / awaitable). A callable that returns a
# non-Awaitable raises TypeError at call time.
pool.snapshot() -> dict[str, dict[str, Any]]
# Returns a point-in-time summary of every resource. Thread-safe without the lock.
# A resource whose cooldown has expired is reported as "healthy" even though the
# stored status only flips on the next acquire.
# Example return value:
# {
#     "key-1": {
#         "status": "healthy",                  # "healthy" | "cooling_down" | "disabled"
#         "in_flight": 2,                       # Current in-flight usage count
#         "consecutive_cooldown": 0,            # Escalation counter
#         "cooldown_seconds_remaining": 0.0,    # Seconds until cooldown expires (0 if healthy)
#         "last_acquired_at": 12345.67,         # time.monotonic() of last acquire
#     },
#     ...
# }
await pool.add(
    resource_id: str,
    value: T,
    *,
    max_in_flight: int | None = None,
) -> Resource[T]
# Add a new healthy resource at runtime. The pool constructs the Resource using
# lifecycle defaults: status="healthy", cooldown_until=0.0, last_acquired_at=0.0,
# consecutive_cooldown=0. Duplicate resource_id raises ValueError. The new
# resource is appended to pool order, so under "primary_backup" it is lower
# priority than existing resources.

await pool.enable(resource_id: str) -> None
# Administratively return a resource to selection. Clears both the disabled state
# and any active cooldown, and resets consecutive_cooldown to 0 (a later failure
# escalates from the first cooldown_table slot). Idempotent on a healthy resource.
# Raises KeyError for an unknown resource_id.

await pool.disable(resource_id: str) -> None
# Administratively remove a resource from selection until enable(). In-flight
# usages are not cancelled (unlike an operation raising DisableResource) -- they
# run to natural completion. Idempotent on an already-disabled resource.
# Raises KeyError for an unknown resource_id.

rotapool.Resource[T]

resource = Resource(
    resource_id: str,
    # resource_id:          Unique identifier for this resource. Must be non-empty.

    value: T,
    # value:                The actual resource object (API key, proxy URL, etc.).

    max_in_flight: int | None = None,
    # max_in_flight:        Maximum concurrent usages. None = unlimited, 1 = exclusive.
    #                       Must be >= 1 or None.

    status: Literal["healthy", "cooling_down", "disabled"] = "healthy",
    # status:               Current health. Managed by the framework -- do not set
    #                       manually.

    cooldown_until: float = 0.0,
    # cooldown_until:       time.monotonic() deadline when status is "cooling_down".
    #                       Managed by the framework -- do not set manually.

    last_acquired_at: float = 0.0,
    # last_acquired_at:     time.monotonic() of most recent acquire. Affects selection
    #                       order (oldest first). Managed by the framework.

    consecutive_cooldown: int = 0,
    # consecutive_cooldown: Number of consecutive CooldownResource signals. Indexes into
    #                       the pool's cooldown_table. Resets to 0 on next success.
    #                       Managed by the framework -- do not set manually.
)

Exceptions

Exception Who raises it Meaning
CooldownResource Your operation Resource temporarily over capacity
DisableResource Your operation Resource permanently bad (only pool.enable() brings it back)
PoolExhausted Framework No eligible resource, max attempts reached, deadline passed, or (with wait_for_cooldown) waiting cannot beat the deadline
raise CooldownResource(
    cooldown_seconds: float | None = None,
    # Explicit cooldown duration (e.g. from Retry-After header). Must be >= 0
    # (negative and NaN raise ValueError at construction).
    # None = use the pool's cooldown_table based on consecutive_cooldown count.

    reason: str | None = None,
    # Free-form string surfaced in the exception message and logs.
)
raise DisableResource(
    reason: str | None = None,
    # Free-form string surfaced in the exception message and logs.
)