Guides
Live streaming
mostlyright.live is the ticker surface — a live feed of fresh METARs from a single source. Use it for dashboards, alerting, or anything that polls and reacts in real time. Both SDKs ship mirrored APIs.
When to use live vs research
Section titled “When to use live vs research”live.stream() and live.latest() are intentionally not the same surface as research(). One picks for the other.
research() | live.stream() / live.latest() | |
|---|---|---|
| Role | BATCH (training pairs, settlement) | STREAM (real-time monitoring) |
| Sources | AWC + IEM + GHCNh + CLI (fused) | ONE of awc | iem |
| Cache writes | Yes (parquet, year-aligned) | No |
| QC | Yes | No |
| Semantics | Point-in-time | Async generator |
| Use case | Backtest, train a model, settle | Watch live, page on threshold |
If you reach for live.stream(), you almost certainly do not want multi-source fusion: a single feed lets you reason about the latency of “what AWC said at 12:01” without IEM’s ~10-min delay smearing the picture.
stream() — the async generator
Section titled “stream() — the async generator”import asyncioimport mostlyright
async def main() -> None: async for row in mostlyright.live.stream("KNYC"): print(row["observed_at"], row["temp_f"])
asyncio.run(main())import { stream } from "@mostlyrightmd/weather";
for await (const row of stream("KNYC")) { console.log(row.observed_at, row.temp_f);}Signature:
- Python:
mostlyright.live.stream(station, *, source=None, poll_seconds=None) -> AsyncIterator[dict] - TypeScript:
stream(station: string, opts?: { source?, pollSeconds?, signal? }): AsyncGenerator<LiveObservation>
Per-row contract:
- Same shape as the canonical
Observationschema —station,observed_at(ISO-8601 UTC),observation_type(METAR/SPECI), plus weather fields. sourcecarries the live-identity tag:"awc.live"or"iem.live"— distinguishes fromresearch()-channel tags ("awc"/"iem").- Duplicate emission is suppressed: the generator yields a row only when
observed_atdiffers from the previous one. Both AWC and IEM serve “the latest METAR” repeatedly between observation cycles; deduping collapses that noise.
latest() — one-shot
Section titled “latest() — one-shot”row = await mostlyright.live.latest("KNYC")print(row["temp_f"])const row = await latest("KNYC");console.log(row.temp_f);Signature:
- Python:
mostlyright.live.latest(station, *, source=None) -> dict - TypeScript:
latest(station: string, opts?: { source? }): Promise<LiveObservation>
latest() shares the per-tick fetch path with stream() but returns exactly once. Use it from cron-style schedulers, or when you want a single fresh observation without managing a generator’s lifecycle.
Unlike stream(), latest() raises NoLiveDataError when the upstream returns no observations. The error’s toDict() payload includes the resolved station and the source identity tag so caller logs can branch.
Sources
Section titled “Sources”| Source | Default? | Endpoint | Typical latency | Use when |
|---|---|---|---|---|
"awc" | yes | aviationweather.gov/api/data/metar | <1 minute | Default — lowest latency |
"iem" | no | mesonet.agron.iastate.edu/cgi-bin/request/asos.py | ~5–10 minutes | AWC is down, or you want an independent cross-check |
Each row carries its source identity on the source field:
- AWC rows →
source == "awc.live" - IEM rows →
source == "iem.live"
The .live suffix distinguishes these from the archive-channel tags ("awc" / "iem") emitted by research() and the historical fetchers.
Polite floors
Section titled “Polite floors”mostlyright.live will not let you poll an upstream faster than its polite floor:
| Source | Floor |
|---|---|
awc | 30s |
iem | 60s |
Calls like stream(..., poll_seconds=10) for an AWC stream raise ValueError("poll_seconds=10 below polite floor 30s for source='awc'") before the first poll. Omitting poll_seconds= uses the floor.
Why the floor:
- Politeness. Both endpoints are public goods. AWC has no documented rate limit; IEM is a university server.
- Useful signal. METARs are issued at roughly hourly cadence (SPECIs are intra-hour). Polling faster than ~30s does not surface new data on most stations — it just costs bandwidth.
Async patterns
Section titled “Async patterns”Python
Section titled “Python”# Standard `async for` consumeasync for row in mostlyright.live.stream("KNYC"): process(row)
# Break cleanly — the polite-floor sleep is `asyncio.sleep`, which# propagates `CancelledError`, so `break` exits without zombie tasks.async for row in mostlyright.live.stream("KNYC"): process(row) if row["temp_f"] > 95: break
# Run multiple stations in parallel — one task per station, polite# floors apply per-task (NOT pooled).async def watch(station: str) -> None: async for row in mostlyright.live.stream(station): process(station, row)
await asyncio.gather(watch("KNYC"), watch("KLAX"), watch("KORD"))TypeScript
Section titled “TypeScript”// Standard `for await` consumefor await (const row of stream("KNYC")) { process(row);}
// Abort via AbortSignal — interrupts the polite-floor sleep promptly.const controller = new AbortController();setTimeout(() => controller.abort(), 60_000);
for await (const row of stream("KNYC", { signal: controller.signal })) { process(row);}
// Multi-station fan-outawait Promise.all( ["KNYC", "KLAX", "KORD"].map(async (station) => { for await (const row of stream(station)) { process(station, row); } }),);Can I write live rows to the parquet cache? No. live.stream() never writes to disk. If you want a durable back-of-tape, call research() separately.
Can I fuse AWC and IEM? Not in this surface — it’s intentionally single-source. Use research() for fused output. If you really want both channels live, run two stream() tasks in parallel and join them yourself.
What if poll_seconds is below the polite floor? ValueError is raised before the first poll with the floor and source named.
Can I run this in a browser? Yes — IEM has CORS open, AWC needs a proxy. See Browser integration for the supported pattern.
See also
Section titled “See also”research()guide — the research surface- Weather observations — the underlying observation contract
- Source identity — why
.livevs archive tags matter - API reference:
mostlyright.live(Python) ·stream/latest(TypeScript)