Skip to content

data_updater_v2.data_clients.base

base.py — the vendor’s plumbing, finished once; a client declares and composes.

Taken from nc-feeds providers/base.py minus the package machinery: no contracts, no normalizers, no finish() / FetchResult — a client’s sections take the RawResponse rows and shape them for the flow. Everything else is the nc-feeds text, plus three moves onto common-infra’s own paths: the limiter gates every PHYSICAL exchange through common-http’s admit hook (retries included; the runner charges only a call’s declared cost beyond one), a client may declare its default RETRY policy, and hit_many runs many one-call asks as one common-http batch.

A client is its declaration tables and its sections. The base turns the tables into a running client: it builds the common-http client from the ClientConfig and the auth the flow hands in, declares the quotas to the limiter the flow hands in and gates every call through it, and drives the runner for pages, windows and the cascade, landing every outcome as a RawResponse. Nothing here reads a config file or a secret.

Sections: 1 · DECLARATIONS what a client states: provider, base URL, endpoints, walks, limits, auth 2 · INIT tables indexed, declarations checked, client · limiter · runner wired 3 · PROPERTIES the read-only face: provider, config, endpoints, walks, scope, auth config 4 · VALIDATION the tables against each other, before any call 5 · CALL hit(): one call, one RawResponse · hit_many(): many, as one batch 6 · WALKS walk(): pages, windows, the cascade, one RawResponse 7 · LIFECYCLE open, close, the async context 8 · DISPLAY

Classes

BaseClient

One vendor, as this container speaks it.

Carried state, for the life of the instance: the ClientConfig it was told; the common-http client it OWNS and closes; the runner; the limiter it BORROWS and never closes, with the Scope its quotas are keyed on; the endpoint and walk tables indexed by name; the auth config in force; a logger. Nothing about any call is kept between calls.

aclose

aclose() -> None

Close the owned client; the borrowed limiter is left alone.

aopen

aopen() -> Self

Open the owned client. The limiter was opened by whoever owns it.

describe

describe() -> str

Every endpoint with its route and its walk in one line each, then the limits.

hit

hit(name: str, key: str | int | None = None, values: Mapping[str, Any] | None = None, **more: Any) -> RawResponse

Exactly one call to a declared endpoint. Reads the rows the way the endpoint’s walk declares them, never pages or windows: a cap hit comes back honest as complete=False. Parameter values come as keywords, or in values for names Python cannot spell.

hit_many

hit_many(name: str, asks: Sequence[Mapping[str, Any]], keys: Sequence[str | int | None] | None = None) -> list[RawResponse]

Many one-call asks of one declared endpoint as ONE common-http batch: one execution owner, its concurrency and retry, results in input order, each read the way hit reads it (rows, cap, absence, fault). Every exchange is admitted by the limiter; a per-item cost beyond one is not charged here (batch endpoints walk).

walk

walk(name: str, key: str | int | None = None, values: Mapping[str, Any] | None = None, stop: Callable[[tuple[Any, ...]], bool] | None = None, **more: Any) -> RawResponse

The whole walk of a declared endpoint for one ask: one hit, a chain of pages, fan-out, windows and the cascade, as its walk declares. Every call is charged at the declared cost before it is made.

Functions

bounded

bounded(calls: Sequence[Callable[[], Awaitable[T]]], limit: int) -> list[T]

Run many independent calls with at most limit in flight, results in input order. The limiter paces the RATE; this bounds how many connections are open at once: 971 EODHD asks gathered at once timed out connecting (ConnectTimeout, 2026-10-06), 5 at a time did not.

per_symbol

per_symbol(symbols: str | Symbols, one: Callable[[str], Awaitable[RawResponse]], limit: int) -> RawResponse | dict[str, RawResponse]

One symbol’s answer, or many symbols’ asked at most limit at a time, symbol -> answer in input order (each symbol once).

retry_after

retry_after(response: Any) -> float | None

How long a refusal says to wait (Retry-After: seconds, or an HTTP date), when it says; None leaves the wait to the backoff.

transient

transient(response: Any) -> bool

A refusal worth asking again: 429 (slow down) or a 5xx (the server’s own trouble).