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.
Files changed (63) hide show
  1. whooing_py-0.1.0/.env.example +2 -0
  2. whooing_py-0.1.0/.github/dependabot.yml +33 -0
  3. whooing_py-0.1.0/.github/workflows/release.yml +88 -0
  4. whooing_py-0.1.0/.github/workflows/test.yml +59 -0
  5. whooing_py-0.1.0/.gitignore +23 -0
  6. whooing_py-0.1.0/.pre-commit-config.yaml +21 -0
  7. whooing_py-0.1.0/.tool-versions +1 -0
  8. whooing_py-0.1.0/AGENTS.md +50 -0
  9. whooing_py-0.1.0/CHANGELOG.md +30 -0
  10. whooing_py-0.1.0/LICENSE +21 -0
  11. whooing_py-0.1.0/PKG-INFO +353 -0
  12. whooing_py-0.1.0/README.md +322 -0
  13. whooing_py-0.1.0/docs/API_COVERAGE.md +76 -0
  14. whooing_py-0.1.0/docs/CLI_DESIGN.md +92 -0
  15. whooing_py-0.1.0/docs/CLI_USAGE.md +275 -0
  16. whooing_py-0.1.0/docs/RELEASE.md +62 -0
  17. whooing_py-0.1.0/pyproject.toml +88 -0
  18. whooing_py-0.1.0/scripts/collect_api_results.py +640 -0
  19. whooing_py-0.1.0/src/whooing/__init__.py +83 -0
  20. whooing_py-0.1.0/src/whooing/_pydantic_models/__init__.py +1 -0
  21. whooing_py-0.1.0/src/whooing/_pydantic_models/auth.py +114 -0
  22. whooing_py-0.1.0/src/whooing/_pydantic_models/base.py +165 -0
  23. whooing_py-0.1.0/src/whooing/_pydantic_models/resources.py +1645 -0
  24. whooing_py-0.1.0/src/whooing/_pydantic_models/responses.py +99 -0
  25. whooing_py-0.1.0/src/whooing/_pydantic_models/types.py +43 -0
  26. whooing_py-0.1.0/src/whooing/_transport.py +130 -0
  27. whooing_py-0.1.0/src/whooing/async_client.py +124 -0
  28. whooing_py-0.1.0/src/whooing/auth.py +666 -0
  29. whooing_py-0.1.0/src/whooing/cli.py +1775 -0
  30. whooing_py-0.1.0/src/whooing/cli_config.py +124 -0
  31. whooing_py-0.1.0/src/whooing/cli_output.py +112 -0
  32. whooing_py-0.1.0/src/whooing/client.py +103 -0
  33. whooing_py-0.1.0/src/whooing/exceptions.py +72 -0
  34. whooing_py-0.1.0/src/whooing/models.py +349 -0
  35. whooing_py-0.1.0/src/whooing/py.typed +1 -0
  36. whooing_py-0.1.0/src/whooing/pydantic_models.py +277 -0
  37. whooing_py-0.1.0/src/whooing/resources.py +770 -0
  38. whooing_py-0.1.0/src/whooing/response.py +108 -0
  39. whooing_py-0.1.0/src/whooing/retry.py +44 -0
  40. whooing_py-0.1.0/src/whooing/types.py +14 -0
  41. whooing_py-0.1.0/tests/.gitkeep +1 -0
  42. whooing_py-0.1.0/tests/fixtures/api_samples/entries_list.json +23 -0
  43. whooing_py-0.1.0/tests/fixtures/api_samples/notifications.json +25 -0
  44. whooing_py-0.1.0/tests/fixtures/api_samples/user.json +12 -0
  45. whooing_py-0.1.0/tests/helpers.py +20 -0
  46. whooing_py-0.1.0/tests/test_api_reference_coverage.py +327 -0
  47. whooing_py-0.1.0/tests/test_api_samples.py +43 -0
  48. whooing_py-0.1.0/tests/test_app_auth.py +174 -0
  49. whooing_py-0.1.0/tests/test_async_client.py +26 -0
  50. whooing_py-0.1.0/tests/test_cli.py +606 -0
  51. whooing_py-0.1.0/tests/test_cli_config.py +53 -0
  52. whooing_py-0.1.0/tests/test_client.py +150 -0
  53. whooing_py-0.1.0/tests/test_integration.py +118 -0
  54. whooing_py-0.1.0/tests/test_models.py +383 -0
  55. whooing_py-0.1.0/tests/test_oauth.py +120 -0
  56. whooing_py-0.1.0/tests/test_package_metadata.py +17 -0
  57. whooing_py-0.1.0/tests/test_pydantic_models.py +708 -0
  58. whooing_py-0.1.0/tests/test_pydantic_response.py +65 -0
  59. whooing_py-0.1.0/tests/test_request_model_fields.py +194 -0
  60. whooing_py-0.1.0/tests/test_resource_paths.py +230 -0
  61. whooing_py-0.1.0/tests/test_resources.py +109 -0
  62. whooing_py-0.1.0/tests/test_retry.py +134 -0
  63. whooing_py-0.1.0/uv.lock +1192 -0
@@ -0,0 +1,2 @@
1
+ WHOOING_API_KEY=
2
+ WHOOING_SECTION_ID=
@@ -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
+ - 일괄 거래 건수와 장기 예산 월별 배분의 공식 요청 제한을 호출 전에 검증합니다.
@@ -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 요청 검증에 사용합니다.