Metadata-Version: 2.4
Name: sendithq
Version: 0.2.0
Summary: SendItWhenever Python SDK — 초정밀 예약 웹훅을 3줄로.
Project-URL: Homepage, https://www.sendit-whenever.com
Project-URL: Documentation, https://www.sendit-whenever.com/docs/sdk-reference
Project-URL: Repository, https://github.com/soft37-git/senditwhenever
Project-URL: Issues, https://github.com/soft37-git/senditwhenever/issues
Author: SendItWhenever
License: MIT
License-File: LICENSE
Keywords: cron,delayed-http,hmac,schedule,scheduler,sendit,sendithq,senditwhenever,webhook
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 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# sendithq — SendItWhenever Python SDK

초정밀 예약 웹훅(HTTP)을 3줄로. Node SDK([`@sendithq/sdk`](https://www.npmjs.com/package/@sendithq/sdk))와 동일한
표면을 Python 관용구로 제공합니다. 동기(`SendIt`) + 비동기(`AsyncSendIt`), 의존성은 `httpx` 하나.

```bash
pip install sendithq
```

## Quickstart (sync)

```python
from sendithq import SendIt

sendit = SendIt("sw_live_xxx")
ref = sendit.schedule(
    url="https://api.myapp.com/hook",
    in_="2h",                    # 또는 fire_at=datetime(...) / "2026-01-01T00:00:00Z"
    payload={"user_id": 42},     # dict → JSON + Content-Type 자동, str → 그대로
    idempotency_key="trial-end:42",
)
print(ref.id, ref.fire_at, ref.status)
```

> `in` 은 Python 예약어라 상대 시간 인자는 **`in_`** 을 씁니다(`in_="30s" | "15m" | "2h" | "1d"`).

## Quickstart (async)

```python
import asyncio
from sendithq import AsyncSendIt

async def main():
    async with AsyncSendIt("sw_live_xxx") as sendit:
        ref = await sendit.schedule(url="https://api.myapp.com/hook", in_="2h")
        print(ref.id)

asyncio.run(main())
```

## Constructor

```python
SendIt(
    api_key,                                  # "sw_live_…" / "sw_test_…" — 형식 오류 시 즉시 예외
    base_url="https://api.sendit-whenever.com",
    timeout=30.0,                             # 초
    max_retries=2,                            # 멱등 경로 한정 재시도
    signing_secret=None,                      # verify_signature 기본 시크릿
)
```

## Methods

| 메서드 | 반환 | 비고 |
|--------|------|------|
| `schedule(url, *, fire_at\|in_, payload, method, headers, idempotency_key, early_fire)` | `ScheduleRef` | `idempotency_key` 있을 때만 자동 재시도. `early_fire` 로 Early Firing opt-in |
| `schedule_many(items)` | `BulkScheduleResult` | 부분성공. `items` 는 schedule() 키의 dict 리스트 |
| `list(*, status, q, limit, offset)` | `ListResult` | 멱등 재시도 |
| `get(id)` | `Schedule` | `fired_at`·`offset_ms`(실측 정시성, 미발사 시 `None`)·`early_fire` 포함 |
| `get_attempts(id)` | `list[DeliveryAttempt]` | 발송 시도 로그 |
| `reschedule(id, *, fire_at\|in_, early_fire)` | `Schedule` | `scheduled` 상태에서만 |
| `replay(id)` | `ScheduleRef` | 동일 암호문 재발송(비멱등) |
| `clone(id, *, payload, fire_at\|in_, early_fire)` | `ScheduleRef` | 새 페이로드(비멱등) |
| `cancel(id)` | `Schedule` | 멱등 |
| `verify_signature(body, headers, *, secrets\|secret, tolerance_sec)` | `bool` | 나가는 웹훅 HMAC 검증 |
| `signing_secrets.get()` / `.rotate()` | `SigningSecretPair` | 2키 회전 |

`AsyncSendIt` 은 동일 표면이며 메서드가 코루틴입니다(`await`).

## Verifying incoming webhooks

SendItWhenever 가 보내는 웹훅에는 항상 `X-SendIt-Signature` 헤더(HMAC-SHA256)가 붙습니다.
**서명 대상은 raw 본문**이므로 파싱 전 원본 바이트를 넘기세요.

```python
# Flask 예시
from flask import request
from sendithq import SendIt

sendit = SendIt("sw_live_xxx")

@app.post("/hook")
def hook():
    secrets = sendit.signing_secrets.get()           # 무중단 회전 대비 2키
    ok = sendit.verify_signature(
        request.get_data(),                            # raw bytes
        request.headers,
        secrets=[secrets.current.secret, secrets.next.secret],
    )
    if not ok:
        return ("bad signature", 400)
    ...
```

## Errors

모든 실패는 `SendItError(code, message, status)` 를 던집니다.

```python
from sendithq import SendItError

try:
    sendit.get("missing")
except SendItError as err:
    if err.code == "NOT_FOUND":
        ...
```

`code` ∈ `UNAUTHORIZED | FORBIDDEN | NOT_FOUND | VALIDATION | CONFLICT | RATE_LIMITED | INTERNAL | NETWORK`.

## Links

- 문서: <https://www.sendit-whenever.com/docs/sdk-reference>
- 대시보드(API Key 발급): <https://www.sendit-whenever.com>

MIT License.
