whooing-py 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.
- whooing_py-0.1.0/.env.example +2 -0
- whooing_py-0.1.0/.github/dependabot.yml +33 -0
- whooing_py-0.1.0/.github/workflows/release.yml +88 -0
- whooing_py-0.1.0/.github/workflows/test.yml +59 -0
- whooing_py-0.1.0/.gitignore +23 -0
- whooing_py-0.1.0/.pre-commit-config.yaml +21 -0
- whooing_py-0.1.0/.tool-versions +1 -0
- whooing_py-0.1.0/AGENTS.md +50 -0
- whooing_py-0.1.0/CHANGELOG.md +30 -0
- whooing_py-0.1.0/LICENSE +21 -0
- whooing_py-0.1.0/PKG-INFO +353 -0
- whooing_py-0.1.0/README.md +322 -0
- whooing_py-0.1.0/docs/API_COVERAGE.md +76 -0
- whooing_py-0.1.0/docs/CLI_DESIGN.md +92 -0
- whooing_py-0.1.0/docs/CLI_USAGE.md +275 -0
- whooing_py-0.1.0/docs/RELEASE.md +62 -0
- whooing_py-0.1.0/pyproject.toml +88 -0
- whooing_py-0.1.0/scripts/collect_api_results.py +640 -0
- whooing_py-0.1.0/src/whooing/__init__.py +83 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/__init__.py +1 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/auth.py +114 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/base.py +165 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/resources.py +1645 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/responses.py +99 -0
- whooing_py-0.1.0/src/whooing/_pydantic_models/types.py +43 -0
- whooing_py-0.1.0/src/whooing/_transport.py +130 -0
- whooing_py-0.1.0/src/whooing/async_client.py +124 -0
- whooing_py-0.1.0/src/whooing/auth.py +666 -0
- whooing_py-0.1.0/src/whooing/cli.py +1775 -0
- whooing_py-0.1.0/src/whooing/cli_config.py +124 -0
- whooing_py-0.1.0/src/whooing/cli_output.py +112 -0
- whooing_py-0.1.0/src/whooing/client.py +103 -0
- whooing_py-0.1.0/src/whooing/exceptions.py +72 -0
- whooing_py-0.1.0/src/whooing/models.py +349 -0
- whooing_py-0.1.0/src/whooing/py.typed +1 -0
- whooing_py-0.1.0/src/whooing/pydantic_models.py +277 -0
- whooing_py-0.1.0/src/whooing/resources.py +770 -0
- whooing_py-0.1.0/src/whooing/response.py +108 -0
- whooing_py-0.1.0/src/whooing/retry.py +44 -0
- whooing_py-0.1.0/src/whooing/types.py +14 -0
- whooing_py-0.1.0/tests/.gitkeep +1 -0
- whooing_py-0.1.0/tests/fixtures/api_samples/entries_list.json +23 -0
- whooing_py-0.1.0/tests/fixtures/api_samples/notifications.json +25 -0
- whooing_py-0.1.0/tests/fixtures/api_samples/user.json +12 -0
- whooing_py-0.1.0/tests/helpers.py +20 -0
- whooing_py-0.1.0/tests/test_api_reference_coverage.py +327 -0
- whooing_py-0.1.0/tests/test_api_samples.py +43 -0
- whooing_py-0.1.0/tests/test_app_auth.py +174 -0
- whooing_py-0.1.0/tests/test_async_client.py +26 -0
- whooing_py-0.1.0/tests/test_cli.py +606 -0
- whooing_py-0.1.0/tests/test_cli_config.py +53 -0
- whooing_py-0.1.0/tests/test_client.py +150 -0
- whooing_py-0.1.0/tests/test_integration.py +118 -0
- whooing_py-0.1.0/tests/test_models.py +383 -0
- whooing_py-0.1.0/tests/test_oauth.py +120 -0
- whooing_py-0.1.0/tests/test_package_metadata.py +17 -0
- whooing_py-0.1.0/tests/test_pydantic_models.py +708 -0
- whooing_py-0.1.0/tests/test_pydantic_response.py +65 -0
- whooing_py-0.1.0/tests/test_request_model_fields.py +194 -0
- whooing_py-0.1.0/tests/test_resource_paths.py +230 -0
- whooing_py-0.1.0/tests/test_resources.py +109 -0
- whooing_py-0.1.0/tests/test_retry.py +134 -0
- whooing_py-0.1.0/uv.lock +1192 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
time: "09:00"
|
|
9
|
+
timezone: Asia/Seoul
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 7
|
|
12
|
+
open-pull-requests-limit: 5
|
|
13
|
+
groups:
|
|
14
|
+
development-tools:
|
|
15
|
+
dependency-type: development
|
|
16
|
+
commit-message:
|
|
17
|
+
prefix: "ci"
|
|
18
|
+
|
|
19
|
+
- package-ecosystem: github-actions
|
|
20
|
+
directory: /
|
|
21
|
+
schedule:
|
|
22
|
+
interval: weekly
|
|
23
|
+
day: monday
|
|
24
|
+
time: "09:00"
|
|
25
|
+
timezone: Asia/Seoul
|
|
26
|
+
cooldown:
|
|
27
|
+
default-days: 7
|
|
28
|
+
groups:
|
|
29
|
+
github-actions:
|
|
30
|
+
patterns:
|
|
31
|
+
- "*"
|
|
32
|
+
commit-message:
|
|
33
|
+
prefix: "ci"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build distributions
|
|
14
|
+
if: ${{ github.event.release.prerelease == false }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check out repository
|
|
19
|
+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: false
|
|
28
|
+
version: "0.11.19"
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.14"
|
|
34
|
+
|
|
35
|
+
- name: Verify release source and tag
|
|
36
|
+
shell: bash
|
|
37
|
+
env:
|
|
38
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
39
|
+
run: |
|
|
40
|
+
package_version="$(uv version --short)"
|
|
41
|
+
test "${GITHUB_REF_NAME}" = "v${package_version}"
|
|
42
|
+
test "$(git rev-parse HEAD)" = "$(git rev-parse "origin/${DEFAULT_BRANCH}")"
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: uv sync --locked --dev
|
|
46
|
+
|
|
47
|
+
- name: Verify package
|
|
48
|
+
run: |
|
|
49
|
+
uv lock --check
|
|
50
|
+
uv run --locked pre-commit run --all-files
|
|
51
|
+
uv run --locked pytest
|
|
52
|
+
|
|
53
|
+
- name: Build distributions
|
|
54
|
+
run: uv build
|
|
55
|
+
|
|
56
|
+
- name: Check distributions
|
|
57
|
+
run: uv run --locked twine check dist/*
|
|
58
|
+
|
|
59
|
+
- name: Upload distributions
|
|
60
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
61
|
+
with:
|
|
62
|
+
name: python-distributions
|
|
63
|
+
path: dist/
|
|
64
|
+
if-no-files-found: error
|
|
65
|
+
retention-days: 7
|
|
66
|
+
|
|
67
|
+
publish:
|
|
68
|
+
name: Publish distributions
|
|
69
|
+
needs:
|
|
70
|
+
- build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
timeout-minutes: 10
|
|
73
|
+
environment:
|
|
74
|
+
name: pypi
|
|
75
|
+
url: https://pypi.org/p/whooing-py
|
|
76
|
+
permissions:
|
|
77
|
+
id-token: write
|
|
78
|
+
steps:
|
|
79
|
+
- name: Download distributions
|
|
80
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
81
|
+
with:
|
|
82
|
+
name: python-distributions
|
|
83
|
+
path: dist/
|
|
84
|
+
|
|
85
|
+
- name: Publish distributions to PyPI
|
|
86
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
87
|
+
with:
|
|
88
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version:
|
|
19
|
+
- "3.11"
|
|
20
|
+
- "3.12"
|
|
21
|
+
- "3.13"
|
|
22
|
+
- "3.14"
|
|
23
|
+
steps:
|
|
24
|
+
- name: Check out repository
|
|
25
|
+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- name: Install uv
|
|
30
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
version: "0.11.19"
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
37
|
+
with:
|
|
38
|
+
python-version: ${{ matrix.python-version }}
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: uv sync --locked --dev
|
|
42
|
+
|
|
43
|
+
- name: Check lock file
|
|
44
|
+
run: uv lock --check
|
|
45
|
+
|
|
46
|
+
- name: Run ruff
|
|
47
|
+
run: uv run --locked ruff check .
|
|
48
|
+
|
|
49
|
+
- name: Check formatting
|
|
50
|
+
run: uv run --locked ruff format --check .
|
|
51
|
+
|
|
52
|
+
- name: Run mypy
|
|
53
|
+
run: uv run --locked mypy src
|
|
54
|
+
|
|
55
|
+
- name: Run pytest
|
|
56
|
+
run: uv run --locked pytest
|
|
57
|
+
|
|
58
|
+
- name: Build package
|
|
59
|
+
run: uv build
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.idea/
|
|
3
|
+
.vscode/
|
|
4
|
+
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.pyo
|
|
8
|
+
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
.env
|
|
16
|
+
.env.*
|
|
17
|
+
!.env.example
|
|
18
|
+
|
|
19
|
+
.venv/
|
|
20
|
+
dist/
|
|
21
|
+
build/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
reports/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff-check
|
|
5
|
+
name: ruff check
|
|
6
|
+
entry: uv run ruff check .
|
|
7
|
+
language: system
|
|
8
|
+
pass_filenames: false
|
|
9
|
+
types_or: [python, pyi]
|
|
10
|
+
- id: ruff-format
|
|
11
|
+
name: ruff format
|
|
12
|
+
entry: uv run ruff format --check .
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
types_or: [python, pyi]
|
|
16
|
+
- id: mypy
|
|
17
|
+
name: mypy
|
|
18
|
+
entry: uv run mypy src
|
|
19
|
+
language: system
|
|
20
|
+
pass_filenames: false
|
|
21
|
+
types_or: [python, pyi]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
python 3.11.8
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Project Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Commit Rules
|
|
4
|
+
|
|
5
|
+
- Before creating a commit, inspect recent history with `git log --oneline -n 20` and match the existing project convention.
|
|
6
|
+
- Commit messages must use the current convention:
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
type: Korean summary
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- Allowed `type` values follow the repository history:
|
|
13
|
+
- `feat`: user-visible capability or API addition
|
|
14
|
+
- `fix`: bug fix or correctness fix
|
|
15
|
+
- `refactor`: internal restructuring without behavior change
|
|
16
|
+
- `test`: test-only changes or coverage expansion
|
|
17
|
+
- `ci`: CI workflow changes
|
|
18
|
+
- `docs`: documentation-only changes
|
|
19
|
+
|
|
20
|
+
- Use Korean for the summary. Keep it concise and specific.
|
|
21
|
+
- Do not use English imperative-only subjects such as `Add ...` or `Fix ...`.
|
|
22
|
+
- Stage only the files that belong to the intended commit. Do not mix unrelated working-tree changes.
|
|
23
|
+
- If a commit has already been made with a message that does not follow this convention, amend it before continuing when it is still local and safe to amend.
|
|
24
|
+
|
|
25
|
+
## Verification Rules
|
|
26
|
+
|
|
27
|
+
- This repository configures `pre-commit` with local hooks for `ruff check .`,
|
|
28
|
+
`ruff format --check .`, and `mypy src`.
|
|
29
|
+
- Do not rely on pre-commit alone before pushing; it does not run pytest or package builds.
|
|
30
|
+
- Before pushing, run the same checks as GitHub Actions when the change can affect code, tests, packaging, dependencies, or CLI behavior:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
uv sync --locked --dev
|
|
34
|
+
uv lock --check
|
|
35
|
+
uv run --locked pre-commit run --all-files
|
|
36
|
+
uv run --locked ruff check .
|
|
37
|
+
uv run --locked ruff format --check .
|
|
38
|
+
uv run --locked mypy src
|
|
39
|
+
uv run --locked pytest
|
|
40
|
+
uv build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- When a failure appears only in the CI matrix, reproduce the failing Python version locally with `uv run --python <version> ...` before changing code.
|
|
44
|
+
- Integration tests that depend on real Whooing credentials must remain skip-safe when credentials are missing, invalid, or unauthorized.
|
|
45
|
+
|
|
46
|
+
## CLI Documentation Rules
|
|
47
|
+
|
|
48
|
+
- When adding, renaming, or changing CLI commands or authentication behavior, update `README.md` and `docs/CLI_USAGE.md` in the same task.
|
|
49
|
+
- `docs/CLI_USAGE.md` should remain suitable for humans and AI agents that need to choose a read-only command without extra explanation.
|
|
50
|
+
- Prefer read-only CLI examples by default. Clearly mark commands that create, update, delete, send, upload, or mark data.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
이 프로젝트의 주요 변경 사항을 기록합니다.
|
|
4
|
+
|
|
5
|
+
공개 API가 안정화되는 동안에도 버전은 semantic versioning을 기준으로 관리합니다.
|
|
6
|
+
|
|
7
|
+
## 0.1.0 - 2026-07-29
|
|
8
|
+
|
|
9
|
+
- 타입 힌트를 제공하는 후잉 개발자 API 클라이언트 초기 구현을 추가했습니다.
|
|
10
|
+
- 동기/비동기 클라이언트를 추가했습니다.
|
|
11
|
+
- API key, Bearer token, OAuth 1.0a, OAuth 2.0 PKCE, Onetime PIN 인증 헬퍼를
|
|
12
|
+
추가했습니다.
|
|
13
|
+
- Pydantic 응답 모델과 CLI 요청/응답 검증을 추가했습니다.
|
|
14
|
+
- 자주 사용하는 쓰기 API용 요청 모델을 추가했습니다.
|
|
15
|
+
- Typer 기반 CLI를 추가해 인증, 프로필, 범용 API 요청, 주요 리소스 조회와 변경 명령을
|
|
16
|
+
지원합니다.
|
|
17
|
+
- CLI에서 글로벌 환경 변수와 현재 디렉터리 `.env`를 읽도록 하고, 환경 변수 기반 프로필
|
|
18
|
+
저장은 `profile set --from-env`로 명시화했습니다.
|
|
19
|
+
- AI와 자동화 도구가 목적별 CLI 명령을 고를 수 있도록 `docs/CLI_USAGE.md`를 추가했습니다.
|
|
20
|
+
- GitHub Actions와 pre-commit 기반 로컬 검증 구성을 추가했습니다.
|
|
21
|
+
- 재현 가능한 CI를 위해 `uv.lock`을 추적하고 실제 API 통합 테스트를 분리했습니다.
|
|
22
|
+
- PyPI Trusted Publishing 기반의 태그 검증 및 배포 자동화를 추가했습니다.
|
|
23
|
+
- 사용자, 섹션, 계정, 거래, 장기 목표, 반복 거래, 포스트잇, 메시지, 게시판 요청
|
|
24
|
+
모델의 필드명을 현재 후잉 개발자 API 문서와 일치시켰습니다.
|
|
25
|
+
- 성공 및 OAuth 응답이 JSON 객체가 아닐 때 패키지 응답 예외로 정규화합니다.
|
|
26
|
+
- 중복 생성을 방지하기 위해 POST 요청은 기본 자동 재시도 대상에서 제외합니다.
|
|
27
|
+
- OAuth2 token 폐기의 빈 성공 응답과 OAuth1 request-token의 callback redirect 응답을
|
|
28
|
+
지원합니다.
|
|
29
|
+
- 빈 명시 옵션이나 환경 변수를 인증 정보로 사용하지 않도록 인증 입력 경계를 강화했습니다.
|
|
30
|
+
- 일괄 거래 건수와 장기 예산 월별 배분의 공식 요청 제한을 호출 전에 검증합니다.
|
whooing_py-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 flynn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whooing-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client library for the Whooing Developer API.
|
|
5
|
+
Project-URL: API-Documentation, https://whooing.com/api/docs
|
|
6
|
+
Project-URL: Changelog, https://github.com/flynnpark/whooing-py/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: CLI-Documentation, https://github.com/flynnpark/whooing-py/blob/main/docs/CLI_USAGE.md
|
|
8
|
+
Project-URL: Issues, https://github.com/flynnpark/whooing-py/issues
|
|
9
|
+
Project-URL: Repository, https://github.com/flynnpark/whooing-py
|
|
10
|
+
Author: flynn
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: accounting,api,client,finance,whooing
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: httpx<1,>=0.28
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
28
|
+
Requires-Dist: python-dotenv<2,>=1.0
|
|
29
|
+
Requires-Dist: typer<1,>=0.12
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# whooing-py
|
|
33
|
+
|
|
34
|
+
[후잉가계부(Whooing)](https://whooing.com)의
|
|
35
|
+
[개발자 API](https://whooing.com/api/docs)를 위한 비공식 Python 클라이언트
|
|
36
|
+
라이브러리입니다.
|
|
37
|
+
|
|
38
|
+
## 상태
|
|
39
|
+
|
|
40
|
+
공식 문서 기준의 1차 클라이언트 구현이 들어간 상태입니다. 동기/비동기 클라이언트, API
|
|
41
|
+
key/Bearer/OAuth 1.0a 인증 헤더, OAuth 1.0a 앱 인증 플로우, Onetime PIN, OAuth 2.0
|
|
42
|
+
PKCE 토큰 헬퍼, 주요 API 리소스 호출 메서드, 응답 메타데이터와 오류 매핑을 제공합니다.
|
|
43
|
+
|
|
44
|
+
응답 스키마는 API 전역에서 폭이 넓기 때문에 현재는 `ApiResponse[JsonValue]` 형태로
|
|
45
|
+
`results`, `rest_of_api`, `code`, 원본 `raw`를 보존합니다. 자주 쓰는 도메인부터 별도
|
|
46
|
+
모델을 점진적으로 얹을 수 있는 구조입니다.
|
|
47
|
+
|
|
48
|
+
## 설치
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
python -m pip install whooing-py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Python 3.11 이상을 지원합니다.
|
|
55
|
+
|
|
56
|
+
## 도구
|
|
57
|
+
|
|
58
|
+
- Python: `asdf`로 관리
|
|
59
|
+
- 패키지 및 환경 관리: `uv`
|
|
60
|
+
- 빌드 백엔드: `hatchling`
|
|
61
|
+
- 정적 분석: `ruff`, `mypy`
|
|
62
|
+
- 테스트: `pytest`
|
|
63
|
+
|
|
64
|
+
## 설정
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
asdf install
|
|
68
|
+
uv sync --locked --dev
|
|
69
|
+
uv run --locked pre-commit install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
CLI와 응답 검증 경로가 Pydantic 모델을 사용하므로 `pydantic`은 기본 의존성입니다.
|
|
73
|
+
|
|
74
|
+
## 개발
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
uv lock --check
|
|
78
|
+
uv run --locked pre-commit run --all-files
|
|
79
|
+
uv run --locked ruff check .
|
|
80
|
+
uv run --locked ruff format --check .
|
|
81
|
+
uv run --locked mypy src
|
|
82
|
+
uv run --locked pytest
|
|
83
|
+
uv build
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
기본 테스트 실행에서는 실제 후잉 API 통합 테스트를 제외합니다. 통합 테스트는 환경 변수가
|
|
87
|
+
있을 때 명시적으로 실행합니다.
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
WHOOING_API_KEY=... WHOOING_SECTION_ID=... uv run --locked pytest -m integration
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
이 프로젝트에서만 자동으로 인식되게 하려면 `.env.example`을 참고해 `.env`를 만듭니다.
|
|
94
|
+
`.env`는 Git에서 무시됩니다.
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
WHOOING_API_KEY=발급된_인증키
|
|
98
|
+
WHOOING_SECTION_ID=s123
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
이후에는 별도 export 없이 실행할 수 있습니다.
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
uv run --locked pytest -m integration
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 사용 예시
|
|
108
|
+
|
|
109
|
+
API key 기반 동기 호출:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from whooing import WhooingClient
|
|
113
|
+
|
|
114
|
+
with WhooingClient(api_key="발급된_인증키") as client:
|
|
115
|
+
sections = client.sections.list()
|
|
116
|
+
print(sections.rest_of_api)
|
|
117
|
+
print(sections.results)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Bearer token 기반 비동기 호출:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from whooing import AsyncWhooingClient
|
|
124
|
+
|
|
125
|
+
async with AsyncWhooingClient(access_token="access-token") as client:
|
|
126
|
+
response = await client.entries.list(section_id="s123", limit=20)
|
|
127
|
+
print(response.results)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
OAuth 2.0 PKCE 토큰 교환:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from whooing import OAuth2TokenClient
|
|
134
|
+
from whooing.auth import build_authorization_url, create_pkce_challenge
|
|
135
|
+
|
|
136
|
+
challenge = create_pkce_challenge()
|
|
137
|
+
url = build_authorization_url(
|
|
138
|
+
client_id="app_id",
|
|
139
|
+
redirect_uri="http://localhost/callback",
|
|
140
|
+
scopes=["read", "write"],
|
|
141
|
+
state="csrf-token",
|
|
142
|
+
challenge=challenge,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
with OAuth2TokenClient() as oauth:
|
|
146
|
+
token = oauth.exchange_code(
|
|
147
|
+
client_id="app_id",
|
|
148
|
+
code="callback-code",
|
|
149
|
+
redirect_uri="http://localhost/callback",
|
|
150
|
+
code_verifier=challenge.verifier,
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
OAuth 1.0a 앱 인증:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from whooing import AppAuthClient, OAuth1aAuth, WhooingClient
|
|
158
|
+
|
|
159
|
+
with AppAuthClient() as app_auth:
|
|
160
|
+
request_token = app_auth.request_token(
|
|
161
|
+
app_id="app_id",
|
|
162
|
+
app_secret="app_secret",
|
|
163
|
+
callback_uri="http://localhost/callback",
|
|
164
|
+
)
|
|
165
|
+
authorize_url = app_auth.build_authorization_url(token=request_token.token)
|
|
166
|
+
access_token = app_auth.access_token(
|
|
167
|
+
app_id="app_id",
|
|
168
|
+
app_secret="app_secret",
|
|
169
|
+
token=request_token.token,
|
|
170
|
+
pin="pin",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
auth = OAuth1aAuth(
|
|
174
|
+
app_id="app_id",
|
|
175
|
+
app_secret="app_secret",
|
|
176
|
+
token=access_token.token,
|
|
177
|
+
token_secret=access_token.token_secret,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
with WhooingClient(auth=auth) as client:
|
|
181
|
+
user = client.users.get()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Onetime PIN 인증:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from whooing import AppAuthClient
|
|
188
|
+
|
|
189
|
+
with AppAuthClient() as app_auth:
|
|
190
|
+
access_token = app_auth.access_token_by_onetime(
|
|
191
|
+
app_id="app_id",
|
|
192
|
+
app_secret="app_secret",
|
|
193
|
+
onetime_pin="pin",
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Pydantic 모델로 응답 파싱:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from whooing import WhooingClient
|
|
201
|
+
from whooing.pydantic_models import Section
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
with WhooingClient(api_key="발급된_인증키") as client:
|
|
205
|
+
response = client.sections.list()
|
|
206
|
+
sections = response.parse_results_as(list[Section])
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
단일 객체 응답은 `response.parse_results(Model)`로, 전체 API 응답은
|
|
210
|
+
`response.parse(ResponseModel)`로 검증할 수 있습니다. 공식 리소스별 모델은
|
|
211
|
+
`whooing.pydantic_models`에 모아 두었습니다.
|
|
212
|
+
|
|
213
|
+
공통 응답 규칙까지 엄밀하게 검증하려면 strict envelope 모델을 사용할 수 있습니다.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from whooing import WhooingClient
|
|
217
|
+
from whooing.pydantic_models import User, WhooingSuccessResponse
|
|
218
|
+
|
|
219
|
+
with WhooingClient(api_key="발급된_인증키") as client:
|
|
220
|
+
response = client.users.get()
|
|
221
|
+
parsed = response.parse(WhooingSuccessResponse[User])
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`whooing.pydantic_models`에는 일반 API의 성공/204/오류 응답, OAuth 오류, OAuth2 토큰,
|
|
225
|
+
OAuth2 metadata, OAuth1 토큰 응답 모델도 포함되어 있습니다.
|
|
226
|
+
|
|
227
|
+
요청 모델 사용:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from whooing import EntryInput, WhooingClient
|
|
231
|
+
|
|
232
|
+
entry = EntryInput(
|
|
233
|
+
entry_date=20260607,
|
|
234
|
+
left_account="expenses",
|
|
235
|
+
left_account_id="x1",
|
|
236
|
+
right_account="assets",
|
|
237
|
+
right_account_id="x2",
|
|
238
|
+
item="커피",
|
|
239
|
+
money=5000,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
with WhooingClient(api_key="발급된_인증키") as client:
|
|
243
|
+
client.entries.create_entry(section_id="s123", entry=entry)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
파라미터가 많은 API는 `AccountInput`, `BudgetInput`, `PostItInput`, `MessageInput` 같은
|
|
247
|
+
dataclass 요청 모델을 제공합니다. 문서에 없는 필드나 새로 추가된 필드는 기존
|
|
248
|
+
`**fields` 방식 또는 요청 모델의 `extra_fields`로 전달할 수 있습니다.
|
|
249
|
+
|
|
250
|
+
선택적 재시도:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from whooing import RetryPolicy, WhooingClient
|
|
254
|
+
|
|
255
|
+
with WhooingClient(
|
|
256
|
+
api_key="발급된_인증키",
|
|
257
|
+
retry_policy=RetryPolicy(max_attempts=3, backoff_seconds=0.5),
|
|
258
|
+
) as client:
|
|
259
|
+
response = client.users.get()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
기본값은 재시도하지 않습니다. `RetryPolicy`를 명시한 경우에만 HTTP 429와 일시적인 5xx
|
|
263
|
+
응답을 제한된 횟수로 다시 시도합니다. 중복 생성을 막기 위해 POST는 기본 재시도 대상에서
|
|
264
|
+
제외됩니다. POST 재시도가 안전한 작업에서만 `retry_methods`에 `"POST"`를 명시적으로
|
|
265
|
+
추가합니다.
|
|
266
|
+
|
|
267
|
+
## CLI
|
|
268
|
+
|
|
269
|
+
CLI는 Typer 기반으로 제공되며, 프로필 저장, OAuth 헬퍼, 범용 API 요청, SDK 리소스
|
|
270
|
+
명령을 지원합니다. CLI 경계에서는 입력 payload를 Pydantic으로 검증하고, 전용 명령의
|
|
271
|
+
API 응답은 리소스별 Pydantic 응답 모델로 검증한 뒤 출력합니다.
|
|
272
|
+
|
|
273
|
+
```sh
|
|
274
|
+
whooing auth oauth2-url \
|
|
275
|
+
--client-id app_id \
|
|
276
|
+
--redirect-uri http://localhost/callback \
|
|
277
|
+
--scope read \
|
|
278
|
+
--scope write
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
프로필 저장:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
whooing --profile default profile set --api-key 발급된_인증키
|
|
285
|
+
whooing --profile default sections list
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
프로필에는 API key와 access token 중 하나만 저장되며, 다른 인증 방식으로 다시 저장하면
|
|
289
|
+
기존 값은 제거됩니다. 프로필 파일은 소유자만 읽고 쓸 수 있도록 저장됩니다.
|
|
290
|
+
|
|
291
|
+
현재 디렉터리에 `.env`가 있으면 CLI 실행 시 자동으로 읽습니다. 셸에 export된 글로벌 환경
|
|
292
|
+
변수가 있으면 그 값을 우선 사용하고, 없을 때 `.env`의 `WHOOING_API_KEY` 또는
|
|
293
|
+
`WHOOING_ACCESS_TOKEN`을 사용합니다. 환경 변수 값을 프로필에 영구 저장하려면 의도를
|
|
294
|
+
명시해 `--from-env`를 사용합니다.
|
|
295
|
+
|
|
296
|
+
```sh
|
|
297
|
+
whooing profile set --from-env
|
|
298
|
+
whooing sections list
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
직접 API 경로 호출:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
whooing --api-key 발급된_인증키 api request GET sections.json
|
|
305
|
+
whooing --api-key 발급된_인증키 --output table accounts list --section-id s123
|
|
306
|
+
whooing --api-key 발급된_인증키 entries create \
|
|
307
|
+
--section-id s123 \
|
|
308
|
+
--field entry_date=20260620 \
|
|
309
|
+
--field l_account=expenses \
|
|
310
|
+
--field l_account_id=x1 \
|
|
311
|
+
--field r_account=assets \
|
|
312
|
+
--field r_account_id=x2 \
|
|
313
|
+
--field item=커피 \
|
|
314
|
+
--field money=5000
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
목적별 CLI 사용법은 [CLI 사용 가이드](docs/CLI_USAGE.md)를 참고합니다. CLI 확장 방향과
|
|
318
|
+
라이브러리 경계는 [CLI 설계 메모](docs/CLI_DESIGN.md)를 기준으로 관리합니다.
|
|
319
|
+
|
|
320
|
+
## 리소스 구성
|
|
321
|
+
|
|
322
|
+
- `client.users`: 사용자 정보, 사용자 로그, 포인트 로그
|
|
323
|
+
- `client.sections`: 섹션 조회/생성/수정/삭제/정렬
|
|
324
|
+
- `client.accounts`: 항목 조회/생성/수정/삭제/정렬
|
|
325
|
+
- `client.entries`: 거래 조회/입력/수정/삭제, 최근 거래, 외부 입력 파싱, 거래 분석성 API
|
|
326
|
+
- `client.budgets`: 예산, 장기 예산 목표, 월별 자본 목표
|
|
327
|
+
- `client.reports`: 통합 보고서, 요약 보고서, 사용자 정의 보고서 행
|
|
328
|
+
- `client.extras`: 자주입력, 매월입력, 카드 보고서, 캘린더, 포스트잇, 쪽지, 게시판, 업로드, 알림
|
|
329
|
+
|
|
330
|
+
## 커버리지
|
|
331
|
+
|
|
332
|
+
현재 구현은 공식 문서의 인증 플로우와 주요 API 경로를 호출할 수 있는 래퍼를 제공합니다.
|
|
333
|
+
후잉 API 응답은 리소스별 형태가 넓기 때문에 기본 클라이언트 응답은 `ApiResponse[JsonValue]`로
|
|
334
|
+
원본 JSON을 보존하고, 필요한 사용자가 Pydantic 헬퍼로 검증하는 방식을 택했습니다.
|
|
335
|
+
|
|
336
|
+
문서의 특정 엔드포인트 래퍼가 빠져 있더라도 `client.request(...)`와
|
|
337
|
+
`async_client.request(...)`로 직접 호출할 수 있습니다. 누락 래퍼는 테스트 가능한 단위로
|
|
338
|
+
추가하는 것을 기본 방향으로 합니다.
|
|
339
|
+
|
|
340
|
+
세부 구현 현황은 [API 구현 커버리지](docs/API_COVERAGE.md)를 기준으로 관리합니다.
|
|
341
|
+
릴리스 절차는 [릴리스 체크리스트](docs/RELEASE.md)를 따릅니다.
|
|
342
|
+
|
|
343
|
+
## 설계 메모
|
|
344
|
+
|
|
345
|
+
- 런타임 HTTP 처리는 `httpx`를 사용합니다.
|
|
346
|
+
- API 모델은 가능한 범위에서 타입이 없는 딕셔너리 대신 명시적인 타입 구조로 정의합니다.
|
|
347
|
+
- 인증은 API key, Bearer token, OAuth 2.0 PKCE, OAuth 1.0a 앱 인증, OAuth 1.0a 헤더
|
|
348
|
+
생성을 지원합니다.
|
|
349
|
+
- API 응답에는 요청 가능 횟수 정보가 포함되므로, 클라이언트 표면에서도 해당 메타데이터를
|
|
350
|
+
버리지 않고 보존합니다.
|
|
351
|
+
- 동기/비동기 클라이언트가 같은 리소스 경로 생성 로직을 공유하도록 구성합니다.
|
|
352
|
+
- 재시도는 기본 동작으로 강제하지 않고, `RetryPolicy`를 명시한 경우에만 적용합니다.
|
|
353
|
+
- Pydantic은 응답 검증과 CLI 요청 검증에 사용합니다.
|