Metadata-Version: 2.4
Name: sub2api-utils
Version: 0.1.0
Summary: A simple tool to access sub2api
License-Expression: MIT
License-File: LICENSE
Author: GGN_2015
Author-email: neko@jlulug.org
Requires-Python: >=3.10
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Description-Content-Type: text/markdown

# sub2api_utils

A user-side **Python programming interface** for Sub2API (the open-source project
Wei-Shaw/sub2api). Pure standard library, zero third-party dependencies
(Python 3.10+).

No CLI, no GUI — just an importable object-oriented interface.

## Project layout

```
sub2api_utils/
├── sub2api_utils/
│   ├── __init__.py     # public interface exports
│   └── main.py         # implementation (standard library only)
├── pyproject.toml      # packaging metadata
└── README.md
```

## Quick start

```python
from datetime import datetime
from sub2api_utils import Sub2ApiLoginSession, DayPeriod, SimpleStat

BASE_URL = "https://your-sub-to-api-website"

# 1) Log in (the password is validated immediately; raises Sub2APIError if wrong)
session = Sub2ApiLoginSession(BASE_URL, "you@example.com", "your-password")

# 2) List all API keys of this user
keys = session.get_api_key_list()
for k in keys:
    print(k.get_str_form())          # sk-xxx
    print(k.get_user_email())        # the email the key belongs to
    print(k.get_key_name())          # the key's name on the site, e.g. 'Main Key'

# 3) Actual usage (in $) of a key over an arbitrary time range, precise to the hour
key = keys[0]
usd = key.get_stat("your-password",
                   datetime(2026, 9, 25, 14, 0),   # start (inclusive)
                   datetime(2026, 9, 28, 9, 0))     # end (exclusive), [start, end)
print(usd)

# 4) Per-day usage for every day in a date period (reuses the same login session)
daily = key.get_stat_in_period("your-password", DayPeriod(2026, 9, 25, 3))
print(daily)                          # [day1, day2, day3]

# 5) A manually constructed key works too (it logs in lazily on first use)
from sub2api_utils import Sub2ApiKey
manual = Sub2ApiKey(BASE_URL, "you@example.com", "sk-xxx")
print(manual.get_stat("your-password", datetime(2026, 9, 25), datetime(2026, 9, 26)))

# 6) Managing multiple users + a usage matrix
from sub2api_utils import Sub2ApiUserInfoSet
us = Sub2ApiUserInfoSet(base_url=BASE_URL)        # base_url is required
us.add_user_info("a@example.com", "pwd-a")        # password is validated on add
us.add_user_info("b@example.com", "pwd-b")
matrix = us.get_stat_matrix(DayPeriod(2026, 9, 25, 2))
# matrix = { "a@example.com": {"sk-1": [d1, d2], ...}, ... }
us.del_user_info("a@example.com")

# 7) Simple statistics
stat = SimpleStat(matrix["b@example.com"]["sk-2"])
print(stat.average(), stat.variation(), stat.stdvar())
```

## Classes and methods

| Class | Method | Description |
| --- | --- | --- |
| `Sub2ApiLoginSession` | `__init__(base_url, user_email, password, *, timeout=30, verify_ssl=True, timezone="Asia/Shanghai")` | Logs in and validates the password; raises `Sub2APIError` on failure |
| | `get_api_key_list() -> list[Sub2ApiKey]` | All API keys of the current user |
| | `get_api_key_stat(key, begin_time, end_time) -> float` | Actual usage ($) of the key over `[begin, end)` |
| `Sub2ApiKey` | `get_str_form() / get_user_email() / get_base_url()` | Basic info |
| | `get_key_name() -> str \| None` | The key's string name on the site (e.g. `Main Key`) |
| | `get_stat(password, begin_time, end_time) -> float` | Usage (logs in lazily on first call, then reuses the session) |
| | `get_stat_in_period(password, days) -> list[float]` | Per-day usage over the date period |
| `DayPeriod` | `DayPeriod(year, month, day, duration)` + four getters | Start date + duration in days (positive integer) |
| `Sub2ApiUserInfoSet` | `__init__(*, base_url=...)` / `add_user_info(email, password)` / `del_user_info(email)` | Add (validates password) / remove users |
| | `get_api_key_list() -> list[Sub2ApiKey]` | All keys of all users |
| | `get_stat_matrix(days) -> dict[email, dict[key_str, list[float]]]` | Per-user, per-key, per-day usage matrix |
| `SimpleStat` | `foldl / average / variation / stdvar` | Left fold / mean / variance / standard deviation |
| `Sub2APIError` | — | Interface error (carries HTTP status, backend code/reason) |

## Implementation notes (verified against the Wei-Shaw/sub2api source)

The client calls the Sub2API user-side HTTP API:

| Purpose | Endpoint |
| --- | --- |
| Login (username = email) | `POST /api/v1/auth/login` → `data.access_token` |
| Key listing (paginated) | `GET /api/v1/keys`, `Authorization: Bearer <token>` |
| Hourly / daily usage | `GET /api/v1/usage/dashboard/trend?granularity=hour\|day` |
| Usage records (fallback) | `GET /api/v1/usage` (paginated) |

- **Base URL**: no site address is hardcoded in the library. Pass `base_url`
  explicitly to `Sub2ApiLoginSession` / `Sub2ApiKey` / `Sub2ApiUserInfoSet`.
- **Hour precision**: `get_stat` sums the hourly trend buckets over the exact
  `[begin, end)` range, so the boundaries can be precise to the hour (the backend
  only accepts day-precision range inputs; extra buckets are dropped client-side).
- **Key name**: `get_key_name()` reads the `name` field of `/api/v1/keys`.
  Keys from `get_api_key_list()` carry the name; a manually constructed key is
  looked up (and cached) once a login session exists; `None` is returned when
  neither is available.
- **Cost basis**: the value returned is `actual_cost` (the amount actually
  charged, i.e. the real $), not the token count.
- **Fallback**: if the backend has no `dashboard/trend` endpoint, the client
  automatically aggregates the per-record usage from `/api/v1/usage` instead;
  results are equivalent.
- **Timezone**: `timezone` decides where the boundaries of a "day"/"hour" fall
  (default `Asia/Shanghai`). When the system lacks tzdata, a built-in fixed-offset
  table is used, so no `tzdata` package is required.
- **Session reuse**: `Sub2ApiKey.get_stat` / `get_stat_in_period` log in on the
  first call and cache the session for later calls; the session is rebuilt when
  the password changes. If an `access_token` expires (HTTP 401), the client
  re-logs in with the stored password and retries once.
- **2FA**: if the account has TOTP two-factor authentication enabled, the login
  response has no `access_token` and `Sub2APIError` reports it clearly (that flow
  requires `/login/2fa`; this library does not automate it).
- Note: in the class skeleton, `get_stat_in_period` was originally annotated as
  returning `list[str]`; it is implemented as `list[float]` (per-day dollar
  amounts, consistent with the inner `list[float]` of `get_stat_matrix`).

