Metadata-Version: 2.4
Name: wr-meteo
Version: 0.1.0
Summary: Async/sync Python client for WR marine weather HTTP APIs.
Author: WR Team
License: MIT
License-File: LICENSE
Keywords: httpx,maritime,meteo,shipping,weather
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: asgiref>=3.8.0
Requires-Dist: httpx>=0.27.0
Description-Content-Type: text/markdown

# wr-meteo

面向 WR 气象 HTTP 服务的 Python 客户端：支持**点位气象**与**气象因子**查询，提供同步 / 异步两种调用方式，响应解析为类型化的不可变 `dataclass`。

## 特性

- 接口：`GET /point`（点位气象）、`GET /factor`（气象因子）
- 鉴权：`api_key`（Query）、`bearer`（Header）、或任意自定义 Header 名
- 每个方法均有 `*_async` 与同步版本（基于 `asgiref`）
- 请求失败时记录日志并返回安全默认值，不向上抛出 HTTP 异常

## 要求

- Python >= 3.12

## 安装

```bash
pip install wr-meteo
```

本地开发：

```bash
git clone <your-repo-url>
cd wr-meteo
pip install -e .
# 或
uv sync
```

## 快速开始

```python
from wr_meteo import MeteoClient

client = MeteoClient(
    base_url="https://meteo.example.com/api",
    auth="your-secret",
    auth_type="X-API-Key",  # 见下方「鉴权」
    timeout=12.0,
)

# 点位气象（同步）
meteo = client.get_point_meteo(lon=121.5, lat=31.2, ts=1714000000)
print(meteo.temp, meteo.wind.kts if meteo.wind else None)

# 气象因子（同步）
speed, weather_f, current_f, marine = client.get_meteo_factor(
    imo=9123456,
    ts=1714000000,
    lon=121.5,
    lat=31.2,
    speed_knots=12.0,
    bearing=90.0,
    laden=True,
    source="gfs",
)
print(speed, weather_f, current_f, marine.sig_wave.height)
```

### 异步

```python
import asyncio
from wr_meteo import MeteoClient

async def main():
    client = MeteoClient("https://meteo.example.com/api", auth="secret", auth_type="api_key")
    meteo = await client.get_point_meteo_async(lon=121.5, lat=31.2, ts=1714000000)
    factor = await client.get_meteo_factor_async(
        imo=9123456, ts=1714000000, lon=121.5, lat=31.2,
        speed_knots=12.0, bearing=90.0, laden=True, source="gfs",
    )
    print(meteo, factor)

asyncio.run(main())
```

## 鉴权

构造参数 `auth` 为密钥或 token 字符串，`auth_type` 决定如何携带：

| `auth_type` | 行为 | 示例 |
|-------------|------|------|
| `"api_key"`（默认） | Query：`?api_key=<auth>` | 内部 / 简单网关 |
| `"bearer"` | Header：`Authorization: Bearer <auth>` | 开放平台、JWT |
| 其它字符串 | Header：`<auth_type>: <auth>` | `auth_type="X-API-Key"` |

```python
# Query
MeteoClient(base_url, auth="secret", auth_type="api_key")

# Bearer
MeteoClient(base_url, auth="eyJhbGciOi...", auth_type="bearer")

# 自定义 Header
MeteoClient(base_url, auth="secret", auth_type="X-API-Key")
```

鉴权参数保存在 `client.headers` / `client.params`，每次请求与业务 query 合并（`{**client.params, ...}`）；httpx 对**同名** query 以本次请求为准。

## API 说明

### `MeteoClient`

| 构造参数 | 类型 | 说明 |
|----------|------|------|
| `base_url` | `str` | 服务根 URL，如 `https://host/api`（勿以 `/` 结尾，客户端会自动处理） |
| `auth` | `str` | API 密钥或 Bearer token |
| `auth_type` | `str` | 默认 `"api_key"`，见上表 |
| `timeout` | `float` | HTTP 超时秒数，默认 `12.0` |

### `get_point_meteo` / `get_point_meteo_async`

- **路径**：`{base_url}/point`
- **Query**：`lon`, `lat`, `ts`（Unix 时间戳，秒）
- **返回**：`PointMeteo`

### `get_meteo_factor` / `get_meteo_factor_async`

- **路径**：`{base_url}/factor`
- **Query**：`imo`, `ts`, `lon`, `lat`, `speed`, `bearing`, `laden`, `source`
- **返回**：`MeteoResult`，即  
  `Tuple[speed, weather_factor, current_factor, MarineMeteo]`

| 返回值 | 含义 |
|--------|------|
| `speed` | 修正后航速（节） |
| `weather_factor` | 风浪因子 |
| `current_factor` | 海流因子 |
| `MarineMeteo` | 风 / 流 / 有效波等明细 |

### 响应 JSON 约定（摘要）

**`/point`** → `PointMeteo` 字段与 JSON 键一致，例如：

```json
{
  "temp": 25.1,
  "wind": {"kts": 12.0, "degree": 90.0},
  "wave": {"sig": {"height": 2.1, "period": 8.0}},
  "current": {"kts": 0.5},
  "utc": "2024-04-25T12:00:00Z"
}
```

**`/factor`** → 顶层示例：

```json
{
  "speed": 11.2,
  "weather_factor": -0.5,
  "current_factor": -0.3,
  "meteo": {
    "wind": {"kts": 15.0},
    "wave": {"sig": {"height": 2.5}},
    "current": {"kts": 1.0}
  }
}
```

`MarineMeteo.from_dict` 会解析顶层 `sig_wave`，若无则从 `meteo.wave.sig` 读取（与点位接口嵌套结构兼容）。

## 数据模型

导出类型（均可从 `wr_meteo` 直接 import）：

| 类型 | 用途 |
|------|------|
| `PointMeteo` | 点位全量气象（含 `wind` / `wave` / `current`） |
| `MarineMeteo` | 因子接口中的 `meteo` 子结构 |
| `WindData` / `WaveData` / `CurrentData` / `OceanWavesData` | 嵌套子结构 |

子结构字段均为 `Optional`，缺失时为 `None`（`MarineMeteo` 内 wind/current/sig_wave 默认为空 dataclass）。

手动解析：

```python
from wr_meteo import PointMeteo, MarineMeteo

PointMeteo.from_dict(payload)
MarineMeteo.from_dict(meteo_dict)
```

## 错误处理

- 网络错误、超时、非 2xx：写 `ERROR` 日志，**不抛异常**
- `get_point_meteo*` → 空 `PointMeteo()`
- `get_meteo_factor*` → `(speed_knots, 0.0, 0.0, MarineMeteo())`

建议开启日志排查：

```python
import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("wr_meteo").setLevel(logging.DEBUG)
```

## 开发与发布

```bash
# 可编辑安装
pip install -e .

# 构建 wheel / sdist
uv build
# 或: pip install hatchling && python -m hatch build

# 上传 PyPI（需配置 token）
# twine upload dist/*
```

发布前请确认：

1. `pyproject.toml` 中 `authors`、仓库 URL 等信息
2. 根目录 `LICENSE` 文件（建议 MIT）
3. 与服务端约定的 `base_url`、路径、`auth_type` 一致

## 许可证

MIT（请在仓库中附带 `LICENSE` 文件）。
