weather-observation-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weather_observation_cli-0.1.0/.gitignore +61 -0
- weather_observation_cli-0.1.0/PKG-INFO +80 -0
- weather_observation_cli-0.1.0/README.md +71 -0
- weather_observation_cli-0.1.0/pyproject.toml +36 -0
- weather_observation_cli-0.1.0/src/weather_observation_cli/__init__.py +3 -0
- weather_observation_cli-0.1.0/src/weather_observation_cli/client.py +87 -0
- weather_observation_cli-0.1.0/src/weather_observation_cli/errors.py +11 -0
- weather_observation_cli-0.1.0/src/weather_observation_cli/main.py +470 -0
- weather_observation_cli-0.1.0/src/weather_observation_cli/storage.py +171 -0
- weather_observation_cli-0.1.0/tests/test_commands.py +88 -0
- weather_observation_cli-0.1.0/tests/test_platform_release.py +38 -0
- weather_observation_cli-0.1.0/tests/test_storage_and_http.py +97 -0
- weather_observation_cli-0.1.0/uv.lock +701 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 操作系统生成文件
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# 前端产物
|
|
6
|
+
front/node_modules/
|
|
7
|
+
front/dist/
|
|
8
|
+
front/coverage/
|
|
9
|
+
|
|
10
|
+
# 本地发布产物与诊断记录;需求和问题记录仍由 Git 维护
|
|
11
|
+
/.scratch/releases/
|
|
12
|
+
/.scratch/diagnostics/
|
|
13
|
+
|
|
14
|
+
# 已退出版本管理的 Vue 模板示例,仅在现有本地工作区保留
|
|
15
|
+
/front/.oxlintrc.json
|
|
16
|
+
/front/src/assets/logo.svg
|
|
17
|
+
/front/src/components/HelloWorld.vue
|
|
18
|
+
/front/src/components/TheWelcome.vue
|
|
19
|
+
/front/src/components/WelcomeItem.vue
|
|
20
|
+
/front/src/components/__tests__/HelloWorld.spec.ts
|
|
21
|
+
/front/src/components/icons/IconCommunity.vue
|
|
22
|
+
/front/src/components/icons/IconDocumentation.vue
|
|
23
|
+
/front/src/components/icons/IconEcosystem.vue
|
|
24
|
+
/front/src/components/icons/IconSupport.vue
|
|
25
|
+
/front/src/components/icons/IconTooling.vue
|
|
26
|
+
|
|
27
|
+
# 后端环境与缓存
|
|
28
|
+
back/.venv/
|
|
29
|
+
back/.pytest_cache/
|
|
30
|
+
back/.pytest-tmp*/
|
|
31
|
+
.pytest-*/
|
|
32
|
+
back/.mypy_cache/
|
|
33
|
+
back/.ruff_cache/
|
|
34
|
+
back/data/
|
|
35
|
+
**/__pycache__/
|
|
36
|
+
*.py[cod]
|
|
37
|
+
|
|
38
|
+
# 独立 CLI 开发环境和构建结果
|
|
39
|
+
cli/.venv/
|
|
40
|
+
cli/.pytest_cache/
|
|
41
|
+
cli/.mypy_cache/
|
|
42
|
+
cli/.ruff_cache/
|
|
43
|
+
cli/dist/
|
|
44
|
+
weather-output/
|
|
45
|
+
|
|
46
|
+
# SQLite 运行数据、备份与临时文件不得进入代码仓库
|
|
47
|
+
*.sqlite
|
|
48
|
+
*.sqlite3
|
|
49
|
+
*.sqlite-shm
|
|
50
|
+
*.sqlite-wal
|
|
51
|
+
*.sqlite3-shm
|
|
52
|
+
*.sqlite3-wal
|
|
53
|
+
|
|
54
|
+
# 脱敏后的首次部署数据库快照
|
|
55
|
+
!deploy/bootstrap/
|
|
56
|
+
!deploy/bootstrap/app-public.sqlite3
|
|
57
|
+
|
|
58
|
+
# 本地配置与密钥
|
|
59
|
+
.env
|
|
60
|
+
.env.*
|
|
61
|
+
!.env.example
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: weather-observation-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 面向 AI 智能体的深圳气象观测客户端
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx<1,>=0.28
|
|
7
|
+
Requires-Dist: keyring<26,>=25
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# weather-observation CLI
|
|
11
|
+
|
|
12
|
+
供 AI 智能体查询深圳气象观测、下载 Excel 报表及管理本人收藏的独立客户端。
|
|
13
|
+
默认连接 `https://weather.cavonxx.com`,只使用普通用户业务能力。
|
|
14
|
+
|
|
15
|
+
推荐通过 uv 安装:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
uv tool install weather-observation-cli
|
|
19
|
+
weather-observation --help
|
|
20
|
+
weather-observation auth login
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
用户在返回的网址中使用已有账号批准,然后再次运行 `auth login`。请求十分钟有效;
|
|
24
|
+
成功登录固定三十天,到期重新授权。无浏览器的服务器可以在其他设备打开网址。
|
|
25
|
+
默认使用系统凭据管理器;不可用时先让用户明确选择,再运行:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
weather-observation auth login --credential-store file
|
|
29
|
+
weather-observation auth status
|
|
30
|
+
weather-observation auth logout
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`file` 使用当前用户私有配置目录,权限限制不是加密,也不能隔离同一用户运行的其他进程。
|
|
34
|
+
macOS 使用 `~/Library/Application Support/weather-observation/`,Linux 使用
|
|
35
|
+
`$XDG_CONFIG_HOME/weather-observation/`(默认 `~/.config/weather-observation/`),
|
|
36
|
+
Windows 使用 `%APPDATA%/weather-observation/`。凭据按服务源隔离。
|
|
37
|
+
退出时先撤销服务端授权;网络失败保留本地状态以便重试。也可在网页“CLI 授权”撤销。
|
|
38
|
+
|
|
39
|
+
## AI 使用顺序
|
|
40
|
+
|
|
41
|
+
1. 读取对应命令的 `--help`,用 `auth status` 检查身份;授权网址只交给用户本人。
|
|
42
|
+
2. `stations list --search 名称` 查找稳定 ID。多项匹配时请用户明确,不自动选首项。
|
|
43
|
+
3. 将自然语言范围转为带 `+08:00` 的 ISO 8601 整点。首尾包含,跨度最多 366 天。
|
|
44
|
+
4. 单站点调用查询、下载或收藏命令;多站点分别调用。
|
|
45
|
+
5. 默认返回绝对文件路径,使用代码读取完整 JSON 后分析。文件属于实际运行环境。
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
weather-observation observations query --station <ID> \
|
|
49
|
+
--start 2026-09-11T00:00:00+08:00 --end 2026-09-11T23:00:00+08:00 \
|
|
50
|
+
--fields temperature,hourly_rain
|
|
51
|
+
weather-observation reports download --station <ID> \
|
|
52
|
+
--start 2026-09-11T00:00:00+08:00 --end 2026-09-11T23:00:00+08:00
|
|
53
|
+
weather-observation favorites list
|
|
54
|
+
weather-observation favorites show <ID>
|
|
55
|
+
weather-observation favorites data <ID>
|
|
56
|
+
weather-observation favorites download <ID>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
要素为 `temperature`(℃)、`humidity`(%)、`wind_speed`(m/s)、`hourly_rain`(mm)、
|
|
60
|
+
`pressure`(hPa)。默认全部,可用 `--fields` 筛选。`null` 表示缺测,`0` 是真实零值。
|
|
61
|
+
每小时保留记录,记录数不等于有效值数;不插值、补零、抽样或默认附带缺测统计。
|
|
62
|
+
|
|
63
|
+
JSON、ZIP 默认保存到执行目录的 `weather-output/`。`--output` 指定文件;已有文件报错,
|
|
64
|
+
不提供覆盖选项。查询 `--stdout` 将完整 JSON 输出到终端,与 `--output` 互斥。
|
|
65
|
+
Excel ZIP 始终包含完整要素的小时、日数据工作簿及完整性说明。
|
|
66
|
+
|
|
67
|
+
收藏保存站点和时间范围,不冻结数据;详情用 `show`,数据用 `data`。修改只提交明确给出的
|
|
68
|
+
`--name`、`--notes`。只有用户明确要求取消指定收藏时才调用 `favorites cancel <ID> --yes`。
|
|
69
|
+
重复创建返回原 ID,不覆盖原名称备注;写请求网络失败时应先检查状态,不盲目重放。
|
|
70
|
+
|
|
71
|
+
所有业务命令输出 `schema_version`、`ok`,成功包含 `data`,失败包含 `error.code/message`。
|
|
72
|
+
退出码:0 成功,2 参数错误,1 其他失败。等待浏览器批准属于成功返回的 pending 状态。
|
|
73
|
+
日志和进度不进入 JSON。帮助不需要登录。服务支持 `--server` 开发覆盖参数(放在子命令前),
|
|
74
|
+
只允许 HTTPS 或本机 HTTP,不跟随服务重定向。
|
|
75
|
+
|
|
76
|
+
## 开发验证
|
|
77
|
+
|
|
78
|
+
Python 3.11 及以上。`uv sync` 后执行 `uv run pytest`、`uv run ruff check src tests`、
|
|
79
|
+
`uv run ruff format --check src tests`、`uv run mypy src tests`,使用 `uv build` 构建。
|
|
80
|
+
macOS、Windows、Linux 为目标平台;实际验证记录见仓库设计文档,未经验证的平台不宣称已通过。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# weather-observation CLI
|
|
2
|
+
|
|
3
|
+
供 AI 智能体查询深圳气象观测、下载 Excel 报表及管理本人收藏的独立客户端。
|
|
4
|
+
默认连接 `https://weather.cavonxx.com`,只使用普通用户业务能力。
|
|
5
|
+
|
|
6
|
+
推荐通过 uv 安装:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv tool install weather-observation-cli
|
|
10
|
+
weather-observation --help
|
|
11
|
+
weather-observation auth login
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
用户在返回的网址中使用已有账号批准,然后再次运行 `auth login`。请求十分钟有效;
|
|
15
|
+
成功登录固定三十天,到期重新授权。无浏览器的服务器可以在其他设备打开网址。
|
|
16
|
+
默认使用系统凭据管理器;不可用时先让用户明确选择,再运行:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
weather-observation auth login --credential-store file
|
|
20
|
+
weather-observation auth status
|
|
21
|
+
weather-observation auth logout
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`file` 使用当前用户私有配置目录,权限限制不是加密,也不能隔离同一用户运行的其他进程。
|
|
25
|
+
macOS 使用 `~/Library/Application Support/weather-observation/`,Linux 使用
|
|
26
|
+
`$XDG_CONFIG_HOME/weather-observation/`(默认 `~/.config/weather-observation/`),
|
|
27
|
+
Windows 使用 `%APPDATA%/weather-observation/`。凭据按服务源隔离。
|
|
28
|
+
退出时先撤销服务端授权;网络失败保留本地状态以便重试。也可在网页“CLI 授权”撤销。
|
|
29
|
+
|
|
30
|
+
## AI 使用顺序
|
|
31
|
+
|
|
32
|
+
1. 读取对应命令的 `--help`,用 `auth status` 检查身份;授权网址只交给用户本人。
|
|
33
|
+
2. `stations list --search 名称` 查找稳定 ID。多项匹配时请用户明确,不自动选首项。
|
|
34
|
+
3. 将自然语言范围转为带 `+08:00` 的 ISO 8601 整点。首尾包含,跨度最多 366 天。
|
|
35
|
+
4. 单站点调用查询、下载或收藏命令;多站点分别调用。
|
|
36
|
+
5. 默认返回绝对文件路径,使用代码读取完整 JSON 后分析。文件属于实际运行环境。
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
weather-observation observations query --station <ID> \
|
|
40
|
+
--start 2026-09-11T00:00:00+08:00 --end 2026-09-11T23:00:00+08:00 \
|
|
41
|
+
--fields temperature,hourly_rain
|
|
42
|
+
weather-observation reports download --station <ID> \
|
|
43
|
+
--start 2026-09-11T00:00:00+08:00 --end 2026-09-11T23:00:00+08:00
|
|
44
|
+
weather-observation favorites list
|
|
45
|
+
weather-observation favorites show <ID>
|
|
46
|
+
weather-observation favorites data <ID>
|
|
47
|
+
weather-observation favorites download <ID>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
要素为 `temperature`(℃)、`humidity`(%)、`wind_speed`(m/s)、`hourly_rain`(mm)、
|
|
51
|
+
`pressure`(hPa)。默认全部,可用 `--fields` 筛选。`null` 表示缺测,`0` 是真实零值。
|
|
52
|
+
每小时保留记录,记录数不等于有效值数;不插值、补零、抽样或默认附带缺测统计。
|
|
53
|
+
|
|
54
|
+
JSON、ZIP 默认保存到执行目录的 `weather-output/`。`--output` 指定文件;已有文件报错,
|
|
55
|
+
不提供覆盖选项。查询 `--stdout` 将完整 JSON 输出到终端,与 `--output` 互斥。
|
|
56
|
+
Excel ZIP 始终包含完整要素的小时、日数据工作簿及完整性说明。
|
|
57
|
+
|
|
58
|
+
收藏保存站点和时间范围,不冻结数据;详情用 `show`,数据用 `data`。修改只提交明确给出的
|
|
59
|
+
`--name`、`--notes`。只有用户明确要求取消指定收藏时才调用 `favorites cancel <ID> --yes`。
|
|
60
|
+
重复创建返回原 ID,不覆盖原名称备注;写请求网络失败时应先检查状态,不盲目重放。
|
|
61
|
+
|
|
62
|
+
所有业务命令输出 `schema_version`、`ok`,成功包含 `data`,失败包含 `error.code/message`。
|
|
63
|
+
退出码:0 成功,2 参数错误,1 其他失败。等待浏览器批准属于成功返回的 pending 状态。
|
|
64
|
+
日志和进度不进入 JSON。帮助不需要登录。服务支持 `--server` 开发覆盖参数(放在子命令前),
|
|
65
|
+
只允许 HTTPS 或本机 HTTP,不跟随服务重定向。
|
|
66
|
+
|
|
67
|
+
## 开发验证
|
|
68
|
+
|
|
69
|
+
Python 3.11 及以上。`uv sync` 后执行 `uv run pytest`、`uv run ruff check src tests`、
|
|
70
|
+
`uv run ruff format --check src tests`、`uv run mypy src tests`,使用 `uv build` 构建。
|
|
71
|
+
macOS、Windows、Linux 为目标平台;实际验证记录见仓库设计文档,未经验证的平台不宣称已通过。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27,<2"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "weather-observation-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "面向 AI 智能体的深圳气象观测客户端"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = ["httpx>=0.28,<1", "keyring>=25,<26"]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
weather-observation = "weather_observation_cli.main:main"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = ["pytest>=8", "ruff>=0.12", "mypy>=1.15"]
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/weather_observation_cli"]
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
pythonpath = ["src"]
|
|
24
|
+
testpaths = ["tests"]
|
|
25
|
+
|
|
26
|
+
[tool.ruff]
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
line-length = 100
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
32
|
+
|
|
33
|
+
[tool.mypy]
|
|
34
|
+
python_version = "3.11"
|
|
35
|
+
strict = true
|
|
36
|
+
files = ["src", "tests"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""限定目标服务且不跟随重定向的 HTTP 客户端。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlsplit
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .errors import CliError
|
|
11
|
+
|
|
12
|
+
DEFAULT_SERVER = "https://weather.cavonxx.com"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def normalize_server(value: str) -> str:
|
|
16
|
+
"""只允许 HTTPS 服务源,开发时允许本机 HTTP。"""
|
|
17
|
+
url = urlsplit(value)
|
|
18
|
+
if url.username or url.password or url.query or url.fragment or url.path not in ("", "/"):
|
|
19
|
+
raise CliError("INVALID_ARGUMENT", "服务地址必须是没有路径、凭据或查询参数的服务源", 2)
|
|
20
|
+
if not url.hostname or (
|
|
21
|
+
url.scheme != "https"
|
|
22
|
+
and not (url.scheme == "http" and url.hostname in ("127.0.0.1", "localhost", "::1"))
|
|
23
|
+
):
|
|
24
|
+
raise CliError("INVALID_ARGUMENT", "服务地址必须使用 HTTPS,本机开发可使用 HTTP", 2)
|
|
25
|
+
try:
|
|
26
|
+
port = url.port
|
|
27
|
+
except ValueError as error:
|
|
28
|
+
raise CliError("INVALID_ARGUMENT", "服务端口无效", 2) from error
|
|
29
|
+
host = f"[{url.hostname}]" if ":" in url.hostname else url.hostname.lower()
|
|
30
|
+
suffix = f":{port}" if port and port != (443 if url.scheme == "https" else 80) else ""
|
|
31
|
+
return f"{url.scheme}://{host}{suffix}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Client:
|
|
35
|
+
"""不自动重试写请求,也不泄露服务响应中的敏感内容。"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, server: str, token: str | None = None) -> None:
|
|
38
|
+
"""保存目标服务和当前独立凭据。"""
|
|
39
|
+
self.server = server
|
|
40
|
+
self.token = token
|
|
41
|
+
|
|
42
|
+
def request(
|
|
43
|
+
self,
|
|
44
|
+
method: str,
|
|
45
|
+
path: str,
|
|
46
|
+
*,
|
|
47
|
+
params: dict[str, Any] | None = None,
|
|
48
|
+
body: dict[str, Any] | None = None,
|
|
49
|
+
binary: bool = False,
|
|
50
|
+
) -> Any:
|
|
51
|
+
"""返回 JSON 或 ZIP,HTTP 与网络失败使用稳定错误码。"""
|
|
52
|
+
headers = {"User-Agent": "weather-observation-cli/0.1.0"}
|
|
53
|
+
if self.token:
|
|
54
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
55
|
+
try:
|
|
56
|
+
with httpx.Client(timeout=120, follow_redirects=False) as client:
|
|
57
|
+
response = client.request(
|
|
58
|
+
method, self.server + path, params=params, json=body, headers=headers
|
|
59
|
+
)
|
|
60
|
+
except httpx.HTTPError as error:
|
|
61
|
+
raise CliError(
|
|
62
|
+
"NETWORK_ERROR", "服务请求未完成;写操作的结果可能不确定,请先查询后再决定是否重试"
|
|
63
|
+
) from error
|
|
64
|
+
codes = {
|
|
65
|
+
401: ("AUTH_REQUIRED", "登录无效或已过期,请运行 auth login"),
|
|
66
|
+
403: ("PERMISSION_DENIED", "没有执行此操作的权限"),
|
|
67
|
+
404: ("NOT_FOUND", "指定对象或服务接口不存在"),
|
|
68
|
+
409: ("CONFLICT", "操作与当前状态冲突,请重新查询"),
|
|
69
|
+
410: ("AUTH_REQUEST_EXPIRED", "授权请求已过期,请重新登录"),
|
|
70
|
+
422: ("INVALID_ARGUMENT", "参数无效,请查看对应命令 --help"),
|
|
71
|
+
429: ("RATE_LIMITED", "请求过于频繁,请稍后重试"),
|
|
72
|
+
}
|
|
73
|
+
if not response.is_success:
|
|
74
|
+
code, message = codes.get(
|
|
75
|
+
response.status_code, ("SERVICE_ERROR", f"服务返回 HTTP {response.status_code}")
|
|
76
|
+
)
|
|
77
|
+
raise CliError(code, message)
|
|
78
|
+
if response.status_code == 204:
|
|
79
|
+
return None
|
|
80
|
+
if binary:
|
|
81
|
+
if not response.content.startswith(b"PK"):
|
|
82
|
+
raise CliError("INVALID_RESPONSE", "服务没有返回有效 ZIP")
|
|
83
|
+
return response.content
|
|
84
|
+
try:
|
|
85
|
+
return response.json()
|
|
86
|
+
except ValueError as error:
|
|
87
|
+
raise CliError("INVALID_RESPONSE", "服务没有返回有效 JSON") from error
|