Universal weather forecast aggregation library with a typed normalized schema.
Project description
omni-weather-forecast-apis
Async Python library that fans out forecast requests across multiple weather providers and normalizes the results into one typed Pydantic schema. It preserves provider-native cadence and time boundaries while converting units and condition codes into a common representation.
Requires Python 3.13 or newer.
๐ Documentation site
Contents
- Features
- Supported Providers
- Quick Start
- Installation
- How It Works
- Configuration
- Library Usage
- CLI Usage
- Observability
- Partial Failures
- Normalized Schema
- Provider Configuration Reference
- SQLite Output
- Extending
- Documentation
- Development
Features
- Multi-provider fan-out with async orchestration and partial-failure tolerance
- Typed normalized schema โ common Pydantic models for minutely, hourly, daily, and alert data
- Plugin architecture โ 13 providers with typed per-provider config validation
- Resilient by default โ retries with exponential backoff (honoring
Retry-After), conditional-request HTTP caching (ETag/Last-Modified/Expires), and explicit connection pool limits - Rate limiting and quotas โ global concurrency and RPS limits with per-provider overrides, plus per-provider daily quota caps
- Secrets from the environment โ reference API keys as
${ENV_VAR}placeholders instead of embedding them in config files - CLI โ loads a TOML config, queries providers, prints a table or JSON, and optionally persists normalized output to SQLite
- Extensible โ response hooks and a documented SQLite feature view for downstream ensemble/verification projects
Supported Providers
Three of the thirteen providers need no API key at all, so you can try the library without signing up for anything.
| Provider | Plugin ID | API key | Minutely | Hourly | Daily | Alerts | Multi-model | Coverage |
|---|---|---|---|---|---|---|---|---|
| Open-Meteo | open_meteo |
Optional | 1 h | 16 d | 16 d | โ | โ | Global |
| MET Norway | met_norway |
None | โ | 9 d | โ | โ | โ | Nordics |
| NWS / NOAA | nws |
None | โ | โ | โ | โ | โ | US only |
| OpenWeather | openweather |
Required | 1 h | 48 h | 8 d | โ | โ | Global |
| WeatherAPI | weatherapi |
Required | โ | 14 d | 14 d | โ | โ | Global |
| Tomorrow.io | tomorrow_io |
Required | 1 h | 5 d | 6 d | โ | โ | Global |
| Visual Crossing | visual_crossing |
Required | โ | 15 d | 15 d | โ | โ | Global |
| Weatherbit | weatherbit |
Required | โ | 10 d | 16 d | โ | โ | Global |
| Meteosource | meteosource |
Required | 1 h | 7 d | 30 d | โ | โ | Global |
| Pirate Weather | pirate_weather |
Required | 1 h | 48 h | 8 d | โ | โ | Global |
| Stormglass | stormglass |
Required | โ | โ | โ | โ | โ | Global |
| Weather Unlocked | weather_unlocked |
Required | โ | โ | โ | โ | โ | Global |
| Google Weather | google_weather |
Required | โ | 10 d | 10 d | โ | โ | Global |
The minutely, hourly, and daily columns give each provider's maximum forecast
horizon. โ
means the granularity is supported but the plugin declares no
horizon bound, and โ means it is not supported at all. Multi-model
providers return several independent forecasts per request โ Open-Meteo exposes
named numerical weather models (best_match, ecmwf_ifs025, โฆ) and Stormglass
returns multiple upstream sources โ which is what makes them useful for
ensembles.
MET Norway and NWS additionally require a user_agent identifying your
application; Weather Unlocked uses an app_id + app_key pair rather than a
single key. Pirate Weather's hourly horizon extends to 168 h when
extend_hourly = true. See the provider configuration
reference for every key each plugin accepts.
Quick Start
Open-Meteo, MET Norway, and NWS need no API keys, so this runs end to end without any signup. No install step โ uv fetches the package into a throwaway environment:
cat > config.toml << 'EOF'
[[providers]]
plugin_id = "open_meteo"
config = { models = ["best_match", "ecmwf_ifs025"] }
[[providers]]
plugin_id = "met_norway"
config = { user_agent = "MyApp/1.0 you@yourdomain.com" }
[[providers]]
plugin_id = "nws"
config = { user_agent = "MyApp/1.0 you@yourdomain.com" }
EOF
uvx --from "omni-weather-forecast-apis[cli]" omni-weather \
--config ./config.toml \
--lat 40.7128 \
--lon -74.0060 \
--sqlite ./forecasts.sqlite
Put a real contact address in user_agent before running this. MET Norway's
terms require one to identify the caller, and their API rejects placeholder
domains such as example.com with a 403 Forbidden.
Which prints:
Run 1 โ 3/3 succeeded
โโโโโโโโโโโโโโณโโโโโโโโโณโโโโโโโโโโณโโโโโโโโโณโโโโโโโโณโโโโโโโโโโโณโโโโโโโโโณโโโโโโโโโโโโโโ
โ Provider โ Status โ Latency โ Hourly โ Daily โ Minutely โ Alerts โ Detail โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ open_meteo โ OK โ 924ms โ 336 โ 14 โ 0 โ - โ 2 source(s) โ
โ met_norway โ OK โ 705ms โ 90 โ 0 โ 0 โ - โ 1 source(s) โ
โ nws โ OK โ 283ms โ 156 โ 8 โ 0 โ - โ 1 source(s) โ
โโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโดโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโโโ
Saved to forecasts.sqlite in 924ms
Open-Meteo contributes two sources because two models were requested. Every
forecast is now in forecasts.sqlite, normalized and ready to
query.
Installation
# As a library dependency
pip install omni-weather-forecast-apis
# With CLI niceties โ rich tables and loguru debug logging
pip install "omni-weather-forecast-apis[cli]"
# Run the CLI without installing anything
uvx --from "omni-weather-forecast-apis[cli]" omni-weather --help
Requires Python 3.13 or newer. The CLI works without the cli extra: table
output falls back to plain text and --debug falls back to stdlib logging.
Contributing to this repository instead? See Development for the
uv sync workflow.
How It Works
- Fan-out โ A
ForecastRequestis dispatched concurrently to every enabled provider using async tasks, bounded by configurable concurrency and rate limits. - Normalize โ Each provider plugin converts its native response into the common
SourceForecastschema, translating units (e.g. Fahrenheit to Celsius, mph to m/s) and mapping provider-specific condition codes to a sharedWeatherConditionenum. - Aggregate โ Results are collected into a single
ForecastResponse. Providers that succeed returnProviderSuccesswith their forecasts; providers that fail returnProviderErrorwith a typed error code. The response always completes, even if some providers fail.
Configuration
The client and CLI both use a TOML configuration file that matches OmniWeatherConfig.
latitude = 40.7128
longitude = -74.0060
sqlite = "forecasts.sqlite"
granularity = ["hourly", "daily"]
language = "en"
include_raw = false
debug = false
default_timeout_ms = 10000
[rate_limiting]
max_in_flight = 10
max_requests_per_second = 20
[retry]
max_attempts = 3 # total attempts per provider fetch; 1 disables retries
initial_backoff_ms = 500
max_backoff_ms = 8000
backoff_multiplier = 2.0
jitter = true
[http]
max_connections = 20
max_keepalive_connections = 10
connect_timeout_ms = 5000
cache_enabled = true # conditional-request HTTP cache (ETag/Last-Modified/Expires)
cache_max_entries = 256
[[providers]]
plugin_id = "open_meteo"
enabled = true
config = { models = ["best_match", "ecmwf_ifs025"] }
[[providers]]
plugin_id = "met_norway"
enabled = true
config = { user_agent = "MyApp/1.0 you@yourdomain.com", variant = "complete" }
[[providers]]
plugin_id = "openweather"
enabled = true
config = { api_key = "${OPENWEATHER_API_KEY}", units = "metric" }
rate_limit_rps = 5
timeout_ms = 8000
max_requests_per_day = 900
Retries
Transient failures โ network errors, timeouts, and HTTP 429 rate limits โ are retried with exponential backoff and jitter. A server-provided Retry-After header is honored; retries are abandoned when it exceeds 60 seconds. Non-transient failures such as auth errors are never retried. Set a per-provider retry table on a registration to override the global policy.
HTTP caching and connection limits
The shared HTTP client is powered by HTTPX2, uses explicit connection pool limits and a connect timeout, and caches GET responses in memory. Fresh responses (Cache-Control: max-age / Expires) are served without a network round-trip; stale responses carrying ETag/Last-Modified validators are revalidated with conditional requests and reused on 304 Not Modified. Responses that declare Vary are only reused for requests sending the same values for the named headers (Vary: * is never cached). Requests carrying Authorization or Cookie headers bypass the shared cache. MET Norway's terms of service require conditional requests and the NWS strongly encourages caching. Disable with cache_enabled = false under [http].
Daily quotas
Most free tiers are capped per day, not per second. Set max_requests_per_day on a provider registration and the client returns a quota_exceeded error once the day's budget (UTC) is spent instead of burning through it. Each fetch attempt counts one request โ retries are real HTTP calls against the provider's cap, so a single forecast() call may consume up to retry.max_attempts units when transient failures trigger retries. This deliberately mirrors provider-side accounting; lower max_attempts if you need a tighter bound per call. The CLI persists counts in the SQLite database (provider_quota_usage table) so limits survive across runs; library users can pass any QuotaTracker implementation with atomic try_consume support (InMemoryQuotaTracker is the default, SqliteQuotaTracker is bundled in omni_weather_forecast_apis.quota).
API keys from environment variables
Any string value inside a provider config block can reference an environment variable instead of embedding a secret:
# whole-string reference
config = { api_key = "${OPENWEATHER_API_KEY}" }
# explicit marker table (equivalent)
config = { api_key = { env = "OPENWEATHER_API_KEY" } }
Resolution happens at client initialization and recurses through nested tables and arrays. A placeholder naming an unset variable becomes a per-provider initialization error; other providers are unaffected. Partial interpolation ("prefix-${VAR}") is not supported โ only whole-string placeholders are resolved.
Library Usage
import asyncio
from omni_weather_forecast_apis import (
ForecastRequest,
Granularity,
OmniWeatherConfig,
ProviderError,
ProviderRegistration,
ProviderId,
ProviderSuccess,
create_omni_weather,
)
async def main() -> None:
config = OmniWeatherConfig(
providers=[
ProviderRegistration(
plugin_id=ProviderId.OPEN_METEO,
config={"models": ["best_match"]},
),
ProviderRegistration(
plugin_id=ProviderId.MET_NORWAY,
config={"user_agent": "MyApp/1.0 you@yourdomain.com"},
),
],
)
async with await create_omni_weather(config) as client:
response = await client.forecast(
ForecastRequest(
latitude=34.2484,
longitude=-117.1931,
granularity=[Granularity.HOURLY, Granularity.DAILY],
),
)
print(response.summary)
# ForecastResponseSummary(total=2, succeeded=2, failed=0)
for result in response.results:
match result:
case ProviderSuccess(provider=pid, forecasts=forecasts):
for fc in forecasts:
for pt in fc.hourly:
print(f"{pid} {pt.timestamp}: {pt.temperature}ยฐC, {pt.condition}")
case ProviderError(provider=pid, error=err):
print(f"{pid} failed: {err.code} โ {err.message}")
asyncio.run(main())
Example output:
ProviderId.OPEN_METEO 2026-03-13 18:00:00+00:00: 12.3ยฐC, WeatherCondition.PARTLY_CLOUDY
ProviderId.OPEN_METEO 2026-03-13 19:00:00+00:00: 11.8ยฐC, WeatherCondition.OVERCAST
ProviderId.MET_NORWAY 2026-03-13 18:00:00+00:00: 12.1ยฐC, WeatherCondition.RAIN
...
CLI Usage
omni-weather \
--config ./config.toml \
--lat 34.2484 \
--lon -117.1931 \
--sqlite ./forecasts.sqlite
# Query only specific providers
omni-weather \
--config ./config.toml \
--lat 34.2484 \
--lon -117.1931 \
--sqlite ./forecasts.sqlite \
--provider open_meteo \
--provider nws \
--granularity hourly
# Emit the full normalized response as JSON (no SQLite required)
omni-weather \
--config ./config.toml \
--lat 34.2484 \
--lon -117.1931 \
--format json | jq '.results[] | {provider, status}'
# Pipe flattened per-point rows into data tools
omni-weather --config ./config.toml --lat 34.2 --lon -117.2 \
--format csv > forecast.csv
omni-weather --config ./config.toml --lat 34.2 --lon -117.2 \
--format ndjson | jq 'select(.type == "forecast_point") | .temperature'
csv and ndjson emit one row/line per forecast data point, flattened with
provider, model, and granularity (minutely / hourly / daily)
columns followed by the normalized point fields. ndjson lines carry a
type field (forecast_point, alert, or provider_error); CSV omits
alerts (noted on stderr) and reports provider errors on stderr only.
| Flag | Required | Default | Description |
|---|---|---|---|
--config PATH |
No | ~/.config/omni_weather_forecast_apis.toml |
Path to TOML configuration file |
--lat FLOAT |
No | config value | Latitude (-90 to 90); overrides config |
--lon FLOAT |
No | config value | Longitude (-180 to 180); overrides config |
--sqlite PATH |
No | config value | SQLite database output path; overrides config. Persistence is skipped when neither is set |
--format FMT |
No | table |
Output format: table (human-readable summary), json (full normalized response), csv (one flattened row per data point), or ndjson (one JSON object per line, typed forecast_point / alert / provider_error) |
--provider ID |
No | all enabled | Restrict to specific provider(s); repeatable |
--granularity GRAN |
No | config value | minutely, hourly, or daily; repeatable |
--language LANG |
No | config value | Provider language preference |
--include-raw |
No | config value | Persist raw provider payloads |
--timeout-ms MS |
No | config value | Override the default timeout; provider-specific timeouts still take precedence |
--debug |
No | config value | Enable verbose debug output to stderr and write a .log file next to the SQLite database, or ./omni-weather.log when SQLite is omitted |
Exit codes: 0 all providers succeeded, 1 at least one provider failed, 2 invalid arguments or configuration/load error.
Observability
Beyond the structured per-provider log events (log_hooks), the client
emits typed metric events for every request attempt, retry, HTTP cache
lookup, and quota consumption. Register any callable as a MetricsHook โ
no extra dependencies required:
from omni_weather_forecast_apis import MetricEvent, MetricKind, create_omni_weather
def record(event: MetricEvent) -> None:
if event.kind is MetricKind.REQUEST_END:
print(event.provider, event.latency_ms, event.error_code)
client = await create_omni_weather(config, metrics_hooks=[record])
MetricKind covers request_start, request_end, retry_scheduled,
cache_hit, cache_miss, quota_consumed, and quota_exhausted. Cache
events carry the request url instead of a provider (the HTTP cache is
shared across providers). response.summary.retries reports how many
retries a forecast() call needed.
For OpenTelemetry, install the otel extra and use the prebuilt bridge:
pip install "omni-weather-forecast-apis[otel]"
from omni_weather_forecast_apis.otel import create_otel_metrics_hook
client = await create_omni_weather(
config,
metrics_hooks=[create_otel_metrics_hook()],
)
The bridge records counters for requests, retries, cache outcomes, and
quota, plus a omni_weather.request.duration_ms histogram.
Partial Failures
The library is designed for partial-failure tolerance. When some providers fail (network errors, rate limits, auth issues), the response still completes with results from the providers that succeeded.
Each entry in response.results is either a ProviderSuccess or ProviderError, distinguished by the status field. The response.summary provides counts at a glance:
response.summary
# ForecastResponseSummary(total=3, succeeded=2, failed=1)
ProviderError includes a typed error.code (AUTH_FAILED, RATE_LIMITED, QUOTA_EXCEEDED, TIMEOUT, NETWORK, PARSE, NOT_AVAILABLE, UNKNOWN), a human-readable error.message, the error.http_status when available, and error.latency_ms for how long the request ran before failing.
The CLI reflects this in exit codes: 0 means all providers succeeded, 1 means at least one failed (but partial results are still written to SQLite), and 2 means invalid arguments or a configuration/load error.
Normalized Schema
All provider responses are normalized into a common set of Pydantic models. Units are standardized: temperatures in ยฐC, wind speeds in m/s, pressure in hPa, precipitation in mm, visibility in km.
WeatherDataPoint (hourly)
| Field | Type | Unit |
|---|---|---|
temperature, apparent_temperature, dew_point |
float | None | ยฐC |
humidity |
float | None | % (0-100) |
wind_speed, wind_gust |
float | None | m/s |
wind_direction |
float | None | degrees |
pressure_sea, pressure_surface |
float | None | hPa |
precipitation, rain, snow, snow_depth |
float | None | mm |
precipitation_probability |
float | None | 0-1 |
cloud_cover, cloud_cover_low, cloud_cover_mid, cloud_cover_high |
float | None | % |
visibility |
float | None | km |
uv_index |
float | None | 0-11+ |
solar_radiation_ghi, solar_radiation_dni, solar_radiation_dhi |
float | None | W/mยฒ |
condition |
WeatherCondition | None | enum |
is_day |
bool | None |
DailyDataPoint
| Field | Type | Unit |
|---|---|---|
date |
date | |
temperature_max, temperature_min |
float | None | ยฐC |
apparent_temperature_max, apparent_temperature_min |
float | None | ยฐC |
wind_speed_max, wind_gust_max |
float | None | m/s |
precipitation_sum, rain_sum, snowfall_sum |
float | None | mm |
precipitation_probability_max |
float | None | 0-1 |
cloud_cover_mean |
float | None | % |
humidity_mean |
float | None | % |
uv_index_max |
float | None | 0-11+ |
sunrise, sunset, moonrise, moonset |
datetime | None | UTC |
moon_phase |
float | None | 0-1 |
daylight_duration |
float | None | seconds |
condition |
WeatherCondition | None | enum |
summary |
str | None |
MinutelyDataPoint
| Field | Type | Unit |
|---|---|---|
precipitation_intensity |
float | None | mm/h |
precipitation_probability |
float | None | 0-1 |
WeatherAlert
| Field | Type |
|---|---|
sender_name |
str |
event |
str |
start, end |
datetime (UTC) |
description |
str |
severity |
EXTREME | SEVERE | MODERATE | MINOR | UNKNOWN |
url |
str | None |
WeatherCondition enum
CLEAR, MOSTLY_CLEAR, PARTLY_CLOUDY, MOSTLY_CLOUDY, OVERCAST, FOG, DRIZZLE, LIGHT_RAIN, RAIN, HEAVY_RAIN, FREEZING_RAIN, LIGHT_SNOW, SNOW, HEAVY_SNOW, SLEET, HAIL, THUNDERSTORM, THUNDERSTORM_RAIN, THUNDERSTORM_HEAVY, DUST, SAND, SMOKE, HAZE, TORNADO, HURRICANE, UNKNOWN
Provider Configuration Reference
Each provider accepts a typed config dict. Required fields are marked with bold.
| Provider | Config Keys |
|---|---|
open_meteo |
api_key?, models (default: ["best_match"]), extra_hourly_vars?, extra_daily_vars? |
met_norway |
user_agent, altitude?, variant ("compact" | "complete", default: "complete") |
nws |
user_agent, grid_override? ({office, grid_x, grid_y}) |
openweather |
api_key, exclude?, units ("standard" | "metric" | "imperial", default: "metric") |
weatherapi |
api_key, days (1-14, default: 7), aqi (default: false), alerts (default: true) |
tomorrow_io |
api_key, fields? |
visual_crossing |
api_key, include (default: "hours,days,alerts") |
weatherbit |
api_key, hours (1-240, default: 48), units ("M" | "S" | "I", default: "M") |
meteosource |
api_key, sections (default: ["current", "hourly", "daily"]) |
pirate_weather |
api_key, extend_hourly (default: false), version ("1" | "2", default: "2") |
stormglass |
api_key, sources (default: ["sg"]), params (list of weather variables) |
weather_unlocked |
app_id, app_key, lang? |
google_weather |
api_key, hours (1-240, default: 48), days (1-10, default: 10) |
SQLite Output
The CLI creates a normalized database with these tables:
| Table | Contents |
|---|---|
forecast_runs |
Request metadata per invocation |
provider_results |
One row per provider outcome (success or error) |
source_forecasts |
One row per model/source forecast within a provider |
minutely_points |
Precipitation intensity at minute intervals |
hourly_points |
Normalized hourly forecast rows |
daily_points |
Normalized daily summary rows |
alerts |
Weather alerts and warnings |
provider_logs |
Per-provider lifecycle log entries (start, retry, success, error) per run |
provider_quota_usage |
Requests per provider per UTC day, used for daily quota enforcement |
The stacking_features SQL view joins hourly points with their provider, model, run cycle, and forecast horizon โ a ready-made feature matrix for downstream ensemble/verification work. Every provider that forecast the same valid time lines up on one axis:
SELECT provider, model, ROUND(temperature, 1) AS temp_c, ROUND(wind_speed, 1) AS wind_ms
FROM stacking_features
WHERE CAST(horizon_hours AS INT) = 24
ORDER BY provider, model;
provider model temp_c wind_ms
---------- ------------ ------ -------
met_norway met_norway 23.2 4.5
nws nws 25.0 4.5
open_meteo best_match 22.0 5.1
open_meteo ecmwf_ifs025 23.2 3.3
Four independent 24-hour-ahead forecasts for the same point and time, already
unit-normalized โ the input a blending or verification model wants. run_cycle
and horizon_hours let you group by forecast age, and fetched_at_unix
distinguishes successive runs.
Extending
Consensus/ensemble forecasting and forecast verification are intended to live in separate packages built on three extension points โ see the Extending guide for details:
-
Response hooks โ sync or async callables that receive every completed
ForecastResponse:async def record_for_verification(response: ForecastResponse) -> None: ... client = await create_omni_weather(config, response_hooks=[record_for_verification])
-
Custom provider plugins โ pass any
WeatherPluginimplementations tocreate_omni_weather(config, plugins=[...])for a per-client plugin set, or register globally withomni_weather_forecast_apis.plugins.register_plugin(the global registry backs the CLI and is the fallback whenpluginsis omitted). -
The SQLite feature matrix โ query the
stacking_featuresview for aligned per-provider hourly forecasts with horizons and run cycles.
Documentation
The documentation site is built with Zensical
(configured in zensical.toml) from the docs/ directory:
uv sync --group docs
uv run zensical serve
Development
# Set up the repository (Python 3.13+)
uv sync
# Lint, format, and type-check
uv run ruff check src --fix
uv run ruff format src tests
uv run pyrefly check src
uv run ty check src
# Dependency, package, and complexity checks
uv run deptry src
uv run pyroma --min 8 .
uv run lizard -Eduplicate -C 27 src
# Tests and the 87% coverage floor
uv run pytest tests/ --cov=src --cov-report=term-missing
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file omni_weather_forecast_apis-0.3.0.tar.gz.
File metadata
- Download URL: omni_weather_forecast_apis-0.3.0.tar.gz
- Upload date:
- Size: 66.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
441edee5db3c367c328b0cda00cc7786edd811a85cc9b408c207045c8e513a0c
|
|
| MD5 |
a800300905447c3bfb219a304a874690
|
|
| BLAKE2b-256 |
68ede7ff7def13521c2ae438e5237be21403611e834fcb7ed5f6e9a256c4a7be
|
File details
Details for the file omni_weather_forecast_apis-0.3.0-py3-none-any.whl.
File metadata
- Download URL: omni_weather_forecast_apis-0.3.0-py3-none-any.whl
- Upload date:
- Size: 90.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c540deaa9eeb3e737652fb7e365f08bc0ea997edbd6d2b8eebc55851ebdccd6
|
|
| MD5 |
dc24bd7d147010fab24d288cac585876
|
|
| BLAKE2b-256 |
91ad1a1b2bf90991c7f7744159e2b565f2ac5d0e207dbd055ff55e8e01e44668
|