kwcli 0.1.0__py3-none-any.whl

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 (62) hide show
  1. kiwoom/__init__.py +47 -0
  2. kiwoom/_data/kiwoom_api_spec.json +66372 -0
  3. kiwoom/core/__init__.py +6 -0
  4. kiwoom/core/auth.py +349 -0
  5. kiwoom/core/client.py +244 -0
  6. kiwoom/core/errors.py +177 -0
  7. kiwoom/core/platform_paths.py +68 -0
  8. kiwoom/core/profiles.py +143 -0
  9. kiwoom/core/runtime.py +153 -0
  10. kiwoom/core/secrets.py +217 -0
  11. kiwoom/core/settings.py +64 -0
  12. kiwoom/core/token_store.py +186 -0
  13. kiwoom/core/types.py +28 -0
  14. kiwoom/core/ws_client.py +262 -0
  15. kiwoom/realtime/__init__.py +30 -0
  16. kiwoom/realtime/decoders.py +171 -0
  17. kiwoom/realtime/events.py +98 -0
  18. kiwoom/realtime/packets.py +65 -0
  19. kiwoom/realtime/schemas.py +76 -0
  20. kiwoom/realtime/stream.py +213 -0
  21. kiwoom/specs.py +272 -0
  22. kiwoom_cli/README.md +671 -0
  23. kiwoom_cli/__init__.py +9 -0
  24. kiwoom_cli/__main__.py +5 -0
  25. kiwoom_cli/argument_maps.py +561 -0
  26. kiwoom_cli/arguments.py +147 -0
  27. kiwoom_cli/auth_context.py +64 -0
  28. kiwoom_cli/banner.py +125 -0
  29. kiwoom_cli/commands/__init__.py +1 -0
  30. kiwoom_cli/commands/auth.py +406 -0
  31. kiwoom_cli/commands/groups.py +281 -0
  32. kiwoom_cli/commands/mapped.py +74 -0
  33. kiwoom_cli/commands/orders.py +158 -0
  34. kiwoom_cli/commands/spec.py +98 -0
  35. kiwoom_cli/commands/stocks.py +100 -0
  36. kiwoom_cli/commands/streams.py +470 -0
  37. kiwoom_cli/doctor.py +324 -0
  38. kiwoom_cli/errors.py +24 -0
  39. kiwoom_cli/executor/__init__.py +33 -0
  40. kiwoom_cli/executor/condition.py +374 -0
  41. kiwoom_cli/executor/rest.py +132 -0
  42. kiwoom_cli/executor/waits.py +34 -0
  43. kiwoom_cli/executor/websocket.py +131 -0
  44. kiwoom_cli/main.py +203 -0
  45. kiwoom_cli/maps/README.md +99 -0
  46. kiwoom_cli/maps/api_commands.csv +209 -0
  47. kiwoom_cli/maps/arguments.csv +731 -0
  48. kiwoom_cli/maps/order_confirmation_commands.csv +13 -0
  49. kiwoom_cli/maps/order_confirmation_fields.csv +71 -0
  50. kiwoom_cli/maps/order_price_policies.csv +47 -0
  51. kiwoom_cli/maps/order_value_labels.csv +28 -0
  52. kiwoom_cli/maps/positional_arguments.csv +21 -0
  53. kiwoom_cli/order_confirmation.py +167 -0
  54. kiwoom_cli/output.py +136 -0
  55. kiwoom_cli/registry.py +95 -0
  56. kiwoom_cli/safety.py +30 -0
  57. kiwoom_cli/setup.py +533 -0
  58. kwcli-0.1.0.dist-info/METADATA +215 -0
  59. kwcli-0.1.0.dist-info/RECORD +62 -0
  60. kwcli-0.1.0.dist-info/WHEEL +4 -0
  61. kwcli-0.1.0.dist-info/entry_points.txt +2 -0
  62. kwcli-0.1.0.dist-info/licenses/LICENSE.md +36 -0
kiwoom_cli/main.py ADDED
@@ -0,0 +1,203 @@
1
+ import argparse
2
+ import re
3
+ import sys
4
+
5
+ import requests
6
+
7
+ from kiwoom import describe_selection
8
+ from kiwoom.core.errors import CredentialsNotFoundError, KiwoomError
9
+ from kiwoom.core.secrets import env_var_names
10
+ from kiwoom_cli.errors import CliInputError, CliInternalError
11
+ from kiwoom_cli.commands.auth import add_auth_parser
12
+ from kiwoom_cli.commands.groups import DOMESTIC_GROUPS, add_group_parser
13
+ from kiwoom_cli.commands.orders import add_orders_parser
14
+ from kiwoom_cli.commands.spec import add_spec_parser
15
+ from kiwoom_cli.commands.stocks import add_stocks_parser
16
+ from kiwoom_cli.commands.streams import add_streams_parser
17
+ from kiwoom_cli.doctor import add_doctor_parser
18
+ from kiwoom_cli.setup import add_setup_parser
19
+
20
+
21
+ class KiwoomArgumentParser(argparse.ArgumentParser):
22
+ def __init__(self, *args, **kwargs):
23
+ kwargs.setdefault("allow_abbrev", False)
24
+ kwargs.setdefault("formatter_class", argparse.RawDescriptionHelpFormatter)
25
+ super().__init__(*args, **kwargs)
26
+
27
+ def add_subparsers(self, *args, **kwargs):
28
+ kwargs.setdefault("parser_class", type(self))
29
+ return super().add_subparsers(*args, **kwargs)
30
+
31
+ def error(self, message: str) -> None:
32
+ self.print_usage(sys.stderr)
33
+ translated = _translate_argparse_error(message)
34
+ self.exit(2, f"{self.prog}: 오류: {translated}\n")
35
+
36
+
37
+ def _translate_argparse_error(message: str) -> str:
38
+ required = re.fullmatch(r"the following arguments are required: (.+)", message)
39
+ if required:
40
+ return f"필수 인자가 빠졌습니다: {required.group(1)}"
41
+
42
+ argument_error = re.fullmatch(r"argument ([^:]+): (.+)", message)
43
+ if argument_error:
44
+ argument, reason = argument_error.groups()
45
+ return f"{argument} 값 오류: {_translate_argparse_error(reason)}"
46
+
47
+ unrecognized = re.fullmatch(r"unrecognized arguments: (.+)", message)
48
+ if unrecognized:
49
+ return f"알 수 없는 인자입니다: {unrecognized.group(1)}"
50
+
51
+ invalid_choice = re.fullmatch(
52
+ r"invalid choice: (.+) \(choose from (.+)\)", message
53
+ )
54
+ if invalid_choice:
55
+ value, choices = invalid_choice.groups()
56
+ return f"지원하지 않는 값입니다: {value}. 가능한 값: {choices}"
57
+
58
+ expected_one = re.fullmatch(r"expected one argument", message)
59
+ if expected_one:
60
+ return "값 하나가 필요합니다."
61
+
62
+ ignored_explicit = re.fullmatch(r"ignored explicit argument (.+)", message)
63
+ if ignored_explicit:
64
+ return f"예상하지 못한 값입니다: {ignored_explicit.group(1)}"
65
+
66
+ return message
67
+
68
+
69
+ def build_parser() -> argparse.ArgumentParser:
70
+ parser = KiwoomArgumentParser(prog="kiwoomcli", description="키움 REST API 도우미 CLI")
71
+ subparsers = parser.add_subparsers(dest="command", required=True)
72
+
73
+ # 전역(시장 무관) 명령
74
+ add_setup_parser(subparsers)
75
+ add_auth_parser(subparsers)
76
+ add_doctor_parser(subparsers)
77
+ add_spec_parser(subparsers)
78
+
79
+ # 국내 시장(OpenAPI) 명령: `kiwoomcli domestic <group> <command>`
80
+ domestic_parser = subparsers.add_parser(
81
+ "domestic", help="국내 시장(OpenAPI) 명령입니다."
82
+ )
83
+ domestic = domestic_parser.add_subparsers(dest="domestic_command", required=True)
84
+ add_stocks_parser(domestic)
85
+ for group_name in (
86
+ "quotes",
87
+ "orderbooks",
88
+ "candles",
89
+ "etfs",
90
+ "elws",
91
+ "investors",
92
+ "rankings",
93
+ "sectors",
94
+ "short-selling",
95
+ "securities-lending",
96
+ "themes",
97
+ ):
98
+ add_group_parser(domestic, DOMESTIC_GROUPS[group_name])
99
+ add_streams_parser(domestic)
100
+ add_group_parser(domestic, DOMESTIC_GROUPS["accounts"])
101
+ add_orders_parser(domestic)
102
+
103
+ return parser
104
+
105
+
106
+ def _normalize_console_encoding() -> None:
107
+ """표준 출력 스트림을 UTF-8로 맞춘다.
108
+
109
+ 한글 Windows 콘솔의 기본 코덱(cp949)은 배너의 박스 문자나 일부 한글
110
+ 출력을 인코딩하지 못해 UnicodeEncodeError로 죽는다. 시작 시 한 번
111
+ UTF-8로 정규화해 CLI 전체 출력을 일관되게 만든다.
112
+ """
113
+ for stream in (sys.stdout, sys.stderr):
114
+ reconfigure = getattr(stream, "reconfigure", None)
115
+ if reconfigure is None:
116
+ continue
117
+ try:
118
+ reconfigure(encoding="utf-8")
119
+ except (ValueError, OSError):
120
+ pass
121
+
122
+
123
+ def main() -> None:
124
+ _normalize_console_encoding()
125
+ parser = build_parser()
126
+ args = parser.parse_args()
127
+
128
+ try:
129
+ args.handler(args)
130
+ except KeyboardInterrupt:
131
+ print("사용자에 의해 작업이 취소되었습니다.", file=sys.stderr)
132
+ raise SystemExit(130)
133
+ except EOFError:
134
+ print("입력이 중단되었습니다.", file=sys.stderr)
135
+ raise SystemExit(1)
136
+ except CliInputError as exc:
137
+ # 사용자 입력/정책 위반: 한글 한 줄, usage 오류 코드(2)로 종료.
138
+ print(str(exc), file=sys.stderr)
139
+ raise SystemExit(2)
140
+ except CliInternalError as exc:
141
+ # 정상 사용으로는 도달 불가한 단언: 버그로 취급해 traceback과 함께 노출.
142
+ print(f"내부 오류: {exc}", file=sys.stderr)
143
+ raise
144
+ except CredentialsNotFoundError as exc:
145
+ print(_format_credentials_error(args, exc), file=sys.stderr)
146
+ raise SystemExit(1)
147
+ except (KiwoomError, OSError, requests.RequestException, ValueError) as exc:
148
+ # API/서버 오류와 라이브러리 ValueError(예: mode 불일치)는 원형 통과.
149
+ print(str(exc), file=sys.stderr)
150
+ raise SystemExit(1)
151
+ except Exception as exc:
152
+ # 예기치 못한 오류 = 버그: 내부 오류로 표시하고 traceback을 남긴다.
153
+ print(f"내부 오류: {exc}", file=sys.stderr)
154
+ raise
155
+
156
+
157
+ def _format_credentials_error(args: argparse.Namespace, exc: CredentialsNotFoundError) -> str:
158
+ """Render a credentials error tailored to how the target was selected.
159
+
160
+ `CredentialsNotFoundError` only carries `mode`, so reconstruct the entry
161
+ path from args/env via `describe_selection`. The `--mode` failure and the
162
+ `--profile` failure are genuinely different problems and get different fixes.
163
+ """
164
+ mode_arg = getattr(args, "mode", None)
165
+ profile_arg = getattr(args, "profile", None)
166
+ try:
167
+ selection = describe_selection(mode_arg, profile=profile_arg)
168
+ except KiwoomError:
169
+ return str(exc)
170
+
171
+ if selection.uses_profile:
172
+ return "\n".join(
173
+ [
174
+ f"계좌 별칭 '{selection.profile}'의 키/시크릿을 찾을 수 없습니다.",
175
+ "",
176
+ "해결:",
177
+ f" kiwoomcli setup {selection.profile}",
178
+ ]
179
+ )
180
+
181
+ appkey_var, secretkey_var = env_var_names(selection.mode)
182
+ return "\n".join(
183
+ [
184
+ f"{selection.mode} mode 키/시크릿을 찾을 수 없습니다.",
185
+ "",
186
+ "현재 인증 컨텍스트:",
187
+ f" 선택 방식: {selection.selection_source}",
188
+ f" 선택 대상: {selection.target_label}",
189
+ " 계좌 별칭: 사용 안 함",
190
+ "",
191
+ "왜 발생했나요?",
192
+ f" {selection.selection_source}은(는) 저장된 계좌 별칭을 사용하지 않습니다.",
193
+ " mode 방식은 APP_KEY / APP_SECRET 환경변수 또는 mode용 자격 증명이 필요합니다.",
194
+ "",
195
+ "해결:",
196
+ " 저장된 별칭을 쓰려면: kiwoomcli <명령> --profile <별칭>",
197
+ f" 환경변수 방식으로 쓰려면: {appkey_var} / {secretkey_var} 를 설정하세요.",
198
+ ]
199
+ )
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()
@@ -0,0 +1,99 @@
1
+ # Kiwoom CLI Maps
2
+
3
+ **GENERATED — do not edit these CSVs by hand.** Every file in this folder is a
4
+ build artifact written by `uv run python utils/build.py`. Human decisions live
5
+ in `registry/` at the repo root: per-API curation in `registry/api_registry.csv`
6
+ and the command-scoped tables in `registry/*.csv`. Edit there, then rebuild;
7
+ `utils/build.py --check` fails on any drift between the two.
8
+
9
+ ## Files
10
+
11
+ - `api_commands.csv`: one row per local Kiwoom API. Machine columns (api_name,
12
+ categories, method, path, required fields) are derived from `api_list.csv` /
13
+ `kiwoom_api_spec.json`; curated columns (cli_group, cli_command, command_path,
14
+ status, coverage_status, safety_policy, canonical_code_option, notes) come
15
+ from `registry/api_registry.csv`.
16
+ - `arguments.csv`: explicit CLI option to Kiwoom request-field mappings for implemented commands (copied from `registry/arguments.csv`).
17
+ - `positional_arguments.csv`: explicit positional shorthand policy by command path (copied from `registry/`).
18
+ - `order_price_policies.csv`: explicit order-type to price/condition-price validation policy for implemented order-write commands (copied from `registry/`).
19
+ - `order_confirmation_commands.csv`, `order_confirmation_fields.csv`, and `order_value_labels.csv`: 미전송 주문 확인 messages, fields, and labels for domestic order-write commands (copied from `registry/`).
20
+
21
+ The table is intentionally explicit. Full CLI coverage means every API ID appears exactly once with an implemented, planned, review, blocked, or unsupported implementation status and a separate coverage status.
22
+
23
+ ## Current Counts
24
+
25
+ | CLI Group | Implementation Status | Coverage Status | Count |
26
+ | --- | --- | --- | ---: |
27
+ | `accounts` | `implemented` | `guarded` | 28 |
28
+ | `auth` | `implemented` | `public` | 2 |
29
+ | `candles` | `implemented` | `public` | 21 |
30
+ | `elws` | `implemented` | `public` | 11 |
31
+ | `etfs` | `implemented` | `public` | 9 |
32
+ | `investors` | `implemented` | `public` | 3 |
33
+ | `orderbooks` | `implemented` | `public` | 2 |
34
+ | `orders` | `implemented` | `guarded` | 17 |
35
+ | `quotes` | `implemented` | `public` | 23 |
36
+ | `rankings` | `implemented` | `public` | 23 |
37
+ | `sectors` | `implemented` | `public` | 6 |
38
+ | `securities-lending` | `implemented` | `public` | 4 |
39
+ | `short-selling` | `implemented` | `public` | 1 |
40
+ | `stocks` | `implemented` | `public` | 31 |
41
+ | `stocks` | `planned` | `public` | 2 |
42
+ | `streams` | `implemented` | `guarded` | 2 |
43
+ | `streams` | `implemented` | `public` | 21 |
44
+ | `themes` | `implemented` | `public` | 2 |
45
+
46
+ ## Implementation Status Meaning
47
+
48
+ - `implemented`: parser/runtime behavior exists now.
49
+ - `planned`: part of the intended CLI implementation backlog.
50
+ - `review`: covered in maps/docs, but command semantics or safety policy still need review.
51
+
52
+ ## Coverage Status Meaning
53
+
54
+ - `public`: ordinary read/query command surface.
55
+ - `guarded`: command is exposed, but output/redaction/account-safety policy applies.
56
+ - `preview-only`: request generation/validation is allowed first; real write submission is blocked (even with `--confirm`) until real-call verification promotes the row to `guarded`.
57
+ - `planned`: mapped for full coverage, but command UX or policy is not fixed yet.
58
+
59
+ ## Column Notes
60
+
61
+ - `cli_group` and `cli_command` are user-facing command taxonomy candidates.
62
+ - `command_path` is the public invocation prefix when a command path is approved, not a raw API-ID command.
63
+ - `status` tracks implementation lifecycle; `coverage_status` tracks exposure/safety posture.
64
+ - `required_body_fields` and `required_header_fields` come from `kiwoom_api_spec.json`.
65
+ - Implemented command request bodies are assembled from `arguments.csv`, not hidden field rewrites.
66
+ - Order price validation is assembled from `order_price_policies.csv`; e.g. `limit` requires `--price`, while `market` forbids it.
67
+ - 미전송 주문 확인 output is assembled from `order_confirmation_*.csv`; without `--confirm` the order API is never called.
68
+ - `canonical_code_option` records the CLI naming rule: prefer `--code` for instrument/sector/theme/symbol-like values and map it internally to Kiwoom field names.
69
+
70
+ ## Validation Scope
71
+
72
+ `.venv/bin/python -m utils.audit.validate_maps` verifies that `api_commands.csv`
73
+ is in sync with `api_list.csv`, `kiwoom_api_spec.json`, and `arguments.csv`: API
74
+ IDs, API names, categories, method/path, required body fields, required header
75
+ fields, implementation status, coverage status, safety policy, implemented
76
+ command argument coverage, argument Kiwoom fields, positional shorthand policy,
77
+ order price policies, and documented count tables are checked directly from the
78
+ source files.
79
+ Count-table drift is checked across `kiwoom_cli/maps/README.md`,
80
+ `docs/cli/api-coverage.md`, and `docs/cli/implementation-status.md`.
81
+
82
+ `.venv/bin/python -m utils.audit.audit_implementation` verifies that implemented
83
+ map rows exist in the argparse command surface, appear in the user-facing docs,
84
+ and that implemented `preview-only`/`order_write` commands do not submit
85
+ unless `--confirm` is supplied for domestic order writes. Domestic order writes
86
+ show 미전송 주문 확인 output without `--confirm` and never call the order API. It also checks the project boundary rules that
87
+ matter to the CLI surface: no broad `kiwoom/apis/` wrapper layer, no
88
+ example-local auth helpers, no customer-facing `uv run kiwoom` invocation, no
89
+ test-double/response-recording terms in implementation/test/example code, and
90
+ resource command modules using the shared executor/runtime facade instead of
91
+ direct network access. Generated `Examples/` files are also compiled
92
+ statically and checked for package-facade runtime acquisition
93
+ (`get_auth`, `get_client`, or `get_ws_client`) rather than direct network/core
94
+ runtime access.
95
+
96
+ `.venv/bin/python -m utils.prove.verify_real_calls --mode demo` is the
97
+ credentialed-environment smoke checker for sanitized safe-read evidence. Without
98
+ credentials it reports a blocked status before network submission instead of
99
+ using any substitute response.