kiwoom-cli 1.1.0__tar.gz → 1.1.2__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 (41) hide show
  1. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/PKG-INFO +1 -1
  2. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/__init__.py +1 -1
  3. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/account.py +6 -6
  4. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/order.py +69 -37
  5. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/config.py +2 -0
  6. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/PKG-INFO +1 -1
  7. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/SOURCES.txt +1 -0
  8. kiwoom_cli-1.1.2/tests/test_auth.py +30 -0
  9. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_client.py +66 -0
  10. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/LICENSE +0 -0
  11. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/README.md +0 -0
  12. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/api_spec.py +0 -0
  13. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/auth.py +0 -0
  14. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/client.py +0 -0
  15. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/__init__.py +0 -0
  16. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/_constants.py +0 -0
  17. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/dashboard.py +0 -0
  18. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/market.py +0 -0
  19. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/stock.py +0 -0
  20. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/stream.py +0 -0
  21. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/commands/watch.py +0 -0
  22. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/formatters.py +0 -0
  23. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/main.py +0 -0
  24. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/output.py +0 -0
  25. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/secure_store.py +0 -0
  26. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli/streaming.py +0 -0
  27. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/dependency_links.txt +0 -0
  28. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/entry_points.txt +0 -0
  29. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/requires.txt +0 -0
  30. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/kiwoom_cli.egg-info/top_level.txt +0 -0
  31. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/pyproject.toml +0 -0
  32. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/setup.cfg +0 -0
  33. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_account.py +0 -0
  34. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_cli.py +0 -0
  35. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_formatters.py +0 -0
  36. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_formatters_property.py +0 -0
  37. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_market.py +0 -0
  38. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_migration.py +0 -0
  39. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_order.py +0 -0
  40. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_secure_store.py +0 -0
  41. {kiwoom_cli-1.1.0 → kiwoom_cli-1.1.2}/tests/test_stock.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiwoom-cli
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: User-friendly CLI for the Kiwoom Securities REST API (키움증권)
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/gejyn14/kiwoom-cli
@@ -1,3 +1,3 @@
1
1
  """Kiwoom Securities REST API CLI."""
2
2
 
3
- __version__ = "1.1.0"
3
+ __version__ = "1.1.2"
@@ -44,12 +44,12 @@ def account_list():
44
44
 
45
45
 
46
46
  @account.command("balance")
47
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT"]), help="거래소 구분")
47
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT"]), help="거래소 구분")
48
48
  @click.option("--delist", "qry_tp", default="0", type=click.Choice(["0", "1"]), help="상장폐지조회구분 (0=전체, 1=제외)")
49
- def balance(stex: str, qry_tp: str):
49
+ def balance(dmst_stex_tp: str, qry_tp: str):
50
50
  """계좌 평가현황 (잔고, 보유종목, 손익). (kt00004)"""
51
51
  with KiwoomClient() as c:
52
- data, _ = c.request("kt00004", {"qry_tp": qry_tp, "dmst_stex_tp": stex})
52
+ data, _ = c.request("kt00004", {"qry_tp": qry_tp, "dmst_stex_tp": dmst_stex_tp})
53
53
  print_account_eval(data)
54
54
 
55
55
 
@@ -98,11 +98,11 @@ def returns():
98
98
 
99
99
 
100
100
  @returns.command("summary")
101
- @click.option("--exchange", "stex", default="all", type=click.Choice(["all", "KRX", "NXT"]), help="거래소구분")
102
- def returns_summary(stex: str):
101
+ @click.option("--exchange", "stex_tp", default="all", type=click.Choice(["all", "KRX", "NXT"]), help="거래소구분")
102
+ def returns_summary(stex_tp: str):
103
103
  """계좌 수익률 조회. (ka10085)"""
104
104
  with KiwoomClient() as c:
105
- data, _ = c.request("ka10085", {"stex_tp": EXCHANGE_ALL_ZERO[stex]})
105
+ data, _ = c.request("ka10085", {"stex_tp": EXCHANGE_ALL_ZERO[stex_tp]})
106
106
  print_generic_table(data, title="계좌 수익률")
107
107
 
108
108
 
@@ -49,7 +49,7 @@ def _order_type_help() -> str:
49
49
  return "주문유형:\n" + "\n".join(lines)
50
50
 
51
51
 
52
- def _show_order_preview(action: str, code: str, qty: int, price: int, order_type: str, stex: str | None = None) -> None:
52
+ def _show_order_preview(action: str, code: str, qty: int, price: int, order_type: str, dmst_stex_tp: str | None = None) -> None:
53
53
  price_str = f"{price:,}원" if price else "시장가"
54
54
  body = (
55
55
  f"[bold]{action} 주문[/]\n\n"
@@ -58,8 +58,8 @@ def _show_order_preview(action: str, code: str, qty: int, price: int, order_type
58
58
  f" 가격: {price_str}\n"
59
59
  f" 유형: {order_type}"
60
60
  )
61
- if stex is not None:
62
- body += f"\n 거래소: {stex}"
61
+ if dmst_stex_tp is not None:
62
+ body += f"\n 거래소: {dmst_stex_tp}"
63
63
  console.print(Panel(
64
64
  body,
65
65
  title="주문 확인",
@@ -67,6 +67,38 @@ def _show_order_preview(action: str, code: str, qty: int, price: int, order_type
67
67
  ))
68
68
 
69
69
 
70
+ def _show_modify_preview(action: str, orig_ord_no: str, code: str, qty: int, price: int, dmst_stex_tp: str | None = None) -> None:
71
+ lines = [
72
+ f" 원주문번호: {orig_ord_no}",
73
+ f" 종목코드: {code}",
74
+ f" 수량: {qty:,}",
75
+ f" 가격: {price:,}원",
76
+ ]
77
+ if dmst_stex_tp is not None:
78
+ lines.append(f" 거래소: {dmst_stex_tp}")
79
+ console.print(Panel(
80
+ f"[bold]{action} 주문[/]\n\n" + "\n".join(lines),
81
+ title="주문 확인",
82
+ border_style="yellow",
83
+ ))
84
+
85
+
86
+ def _show_cancel_preview(action: str, orig_ord_no: str, code: str, qty: int, dmst_stex_tp: str | None = None) -> None:
87
+ qty_str = f"{qty:,}" if qty else "전량"
88
+ lines = [
89
+ f" 원주문번호: {orig_ord_no}",
90
+ f" 종목코드: {code}",
91
+ f" 수량: {qty_str}",
92
+ ]
93
+ if dmst_stex_tp is not None:
94
+ lines.append(f" 거래소: {dmst_stex_tp}")
95
+ console.print(Panel(
96
+ f"[bold]{action} 주문[/]\n\n" + "\n".join(lines),
97
+ title="주문 확인",
98
+ border_style="yellow",
99
+ ))
100
+
101
+
70
102
  # ════════════════════════════════════════════════════════
71
103
  # Top-level order group
72
104
  # ════════════════════════════════════════════════════════
@@ -86,10 +118,10 @@ def order():
86
118
  @click.argument("qty", type=int)
87
119
  @click.option("--price", type=int, default=0, help="주문가격 (시장가 주문시 생략)")
88
120
  @click.option("--type", "order_type", default="market", type=click.Choice(list(ORDER_TYPES.keys())), help="주문유형")
89
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
121
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
90
122
  @click.option("--cond-price", "cond_uv", type=int, default=0, help="조건부가격 (스톱지정가 등)")
91
123
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
92
- def buy(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: int, confirm: bool):
124
+ def buy(code: str, qty: int, price: int, order_type: str, dmst_stex_tp: str, cond_uv: int, confirm: bool):
93
125
  """주식 매수주문 (kt10000).
94
126
 
95
127
  예: kiwoom order buy 005930 10 --price 70000 --type limit --confirm
@@ -97,11 +129,11 @@ def buy(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: in
97
129
  if not confirm:
98
130
  click.confirm("주문을 실행하시겠습니까?", abort=True)
99
131
 
100
- _show_order_preview("매수", code, qty, price, order_type, stex)
132
+ _show_order_preview("매수", code, qty, price, order_type, dmst_stex_tp)
101
133
 
102
134
  with KiwoomClient() as c:
103
135
  data, _ = c.request("kt10000", {
104
- "dmst_stex_tp": stex,
136
+ "dmst_stex_tp": dmst_stex_tp,
105
137
  "stk_cd": code,
106
138
  "ord_qty": str(qty),
107
139
  "ord_uv": str(price) if price else "",
@@ -116,10 +148,10 @@ def buy(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: in
116
148
  @click.argument("qty", type=int)
117
149
  @click.option("--price", type=int, default=0, help="주문가격 (시장가 주문시 생략)")
118
150
  @click.option("--type", "order_type", default="market", type=click.Choice(list(ORDER_TYPES.keys())), help="주문유형")
119
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
151
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
120
152
  @click.option("--cond-price", "cond_uv", type=int, default=0, help="조건부가격 (스톱지정가 등)")
121
153
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
122
- def sell(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: int, confirm: bool):
154
+ def sell(code: str, qty: int, price: int, order_type: str, dmst_stex_tp: str, cond_uv: int, confirm: bool):
123
155
  """주식 매도주문 (kt10001).
124
156
 
125
157
  예: kiwoom order sell 005930 10 --type market --confirm
@@ -127,11 +159,11 @@ def sell(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: i
127
159
  if not confirm:
128
160
  click.confirm("주문을 실행하시겠습니까?", abort=True)
129
161
 
130
- _show_order_preview("매도", code, qty, price, order_type, stex)
162
+ _show_order_preview("매도", code, qty, price, order_type, dmst_stex_tp)
131
163
 
132
164
  with KiwoomClient() as c:
133
165
  data, _ = c.request("kt10001", {
134
- "dmst_stex_tp": stex,
166
+ "dmst_stex_tp": dmst_stex_tp,
135
167
  "stk_cd": code,
136
168
  "ord_qty": str(qty),
137
169
  "ord_uv": str(price) if price else "",
@@ -146,10 +178,10 @@ def sell(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: i
146
178
  @click.argument("code")
147
179
  @click.argument("qty", type=int)
148
180
  @click.argument("price", type=int)
149
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
181
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
150
182
  @click.option("--cond-price", "mdfy_cond_uv", type=int, default=0, help="정정 조건부가격")
151
183
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
152
- def modify(orig_order_no: str, code: str, qty: int, price: int, stex: str, mdfy_cond_uv: int, confirm: bool):
184
+ def modify(orig_order_no: str, code: str, qty: int, price: int, dmst_stex_tp: str, mdfy_cond_uv: int, confirm: bool):
153
185
  """주식 정정주문 (kt10002).
154
186
 
155
187
  예: kiwoom order modify 0000139 005930 1 70000 --confirm
@@ -157,11 +189,11 @@ def modify(orig_order_no: str, code: str, qty: int, price: int, stex: str, mdfy_
157
189
  if not confirm:
158
190
  click.confirm("주문을 실행하시겠습니까?", abort=True)
159
191
 
160
- console.print(f"[yellow]정정 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty} 가격={price:,}")
192
+ _show_modify_preview("정정", orig_order_no, code, qty, price, dmst_stex_tp)
161
193
 
162
194
  with KiwoomClient() as c:
163
195
  data, _ = c.request("kt10002", {
164
- "dmst_stex_tp": stex,
196
+ "dmst_stex_tp": dmst_stex_tp,
165
197
  "orig_ord_no": orig_order_no,
166
198
  "stk_cd": code,
167
199
  "mdfy_qty": str(qty),
@@ -175,9 +207,9 @@ def modify(orig_order_no: str, code: str, qty: int, price: int, stex: str, mdfy_
175
207
  @click.argument("orig_order_no")
176
208
  @click.argument("code")
177
209
  @click.option("--qty", type=int, default=0, help="취소수량 (0=전량취소)")
178
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
210
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
179
211
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
180
- def cancel(orig_order_no: str, code: str, qty: int, stex: str, confirm: bool):
212
+ def cancel(orig_order_no: str, code: str, qty: int, dmst_stex_tp: str, confirm: bool):
181
213
  """주식 취소주문 (kt10003).
182
214
 
183
215
  예: kiwoom order cancel 0000140 005930 --confirm
@@ -185,11 +217,11 @@ def cancel(orig_order_no: str, code: str, qty: int, stex: str, confirm: bool):
185
217
  if not confirm:
186
218
  click.confirm("주문을 실행하시겠습니까?", abort=True)
187
219
 
188
- console.print(f"[yellow]취소 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty or '전량'}")
220
+ _show_cancel_preview("취소", orig_order_no, code, qty, dmst_stex_tp)
189
221
 
190
222
  with KiwoomClient() as c:
191
223
  data, _ = c.request("kt10003", {
192
- "dmst_stex_tp": stex,
224
+ "dmst_stex_tp": dmst_stex_tp,
193
225
  "orig_ord_no": orig_order_no,
194
226
  "stk_cd": code,
195
227
  "cncl_qty": str(qty),
@@ -212,10 +244,10 @@ def credit():
212
244
  @click.argument("qty", type=int)
213
245
  @click.option("--price", type=int, default=0, help="주문가격 (시장가 주문시 생략)")
214
246
  @click.option("--type", "order_type", default="market", type=click.Choice(list(ORDER_TYPES.keys())), help="주문유형")
215
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
247
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
216
248
  @click.option("--cond-price", "cond_uv", type=int, default=0, help="조건부가격")
217
249
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
218
- def credit_buy(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: int, confirm: bool):
250
+ def credit_buy(code: str, qty: int, price: int, order_type: str, dmst_stex_tp: str, cond_uv: int, confirm: bool):
219
251
  """신용 매수주문 (kt10006).
220
252
 
221
253
  예: kiwoom order credit buy 005930 10 --type limit --price 70000 --confirm
@@ -223,11 +255,11 @@ def credit_buy(code: str, qty: int, price: int, order_type: str, stex: str, cond
223
255
  if not confirm:
224
256
  click.confirm("주문을 실행하시겠습니까?", abort=True)
225
257
 
226
- _show_order_preview("신용 매수", code, qty, price, order_type, stex)
258
+ _show_order_preview("신용 매수", code, qty, price, order_type, dmst_stex_tp)
227
259
 
228
260
  with KiwoomClient() as c:
229
261
  data, _ = c.request("kt10006", {
230
- "dmst_stex_tp": stex,
262
+ "dmst_stex_tp": dmst_stex_tp,
231
263
  "stk_cd": code,
232
264
  "ord_qty": str(qty),
233
265
  "ord_uv": str(price) if price else "",
@@ -242,10 +274,10 @@ def credit_buy(code: str, qty: int, price: int, order_type: str, stex: str, cond
242
274
  @click.argument("qty", type=int)
243
275
  @click.option("--price", type=int, default=0, help="주문가격 (시장가 주문시 생략)")
244
276
  @click.option("--type", "order_type", default="market", type=click.Choice(list(ORDER_TYPES.keys())), help="주문유형")
245
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
277
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
246
278
  @click.option("--cond-price", "cond_uv", type=int, default=0, help="조건부가격")
247
279
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
248
- def credit_sell(code: str, qty: int, price: int, order_type: str, stex: str, cond_uv: int, confirm: bool):
280
+ def credit_sell(code: str, qty: int, price: int, order_type: str, dmst_stex_tp: str, cond_uv: int, confirm: bool):
249
281
  """신용 매도주문 (kt10007).
250
282
 
251
283
  예: kiwoom order credit sell 005930 10 --type market --confirm
@@ -253,11 +285,11 @@ def credit_sell(code: str, qty: int, price: int, order_type: str, stex: str, con
253
285
  if not confirm:
254
286
  click.confirm("주문을 실행하시겠습니까?", abort=True)
255
287
 
256
- _show_order_preview("신용 매도", code, qty, price, order_type, stex)
288
+ _show_order_preview("신용 매도", code, qty, price, order_type, dmst_stex_tp)
257
289
 
258
290
  with KiwoomClient() as c:
259
291
  data, _ = c.request("kt10007", {
260
- "dmst_stex_tp": stex,
292
+ "dmst_stex_tp": dmst_stex_tp,
261
293
  "stk_cd": code,
262
294
  "ord_qty": str(qty),
263
295
  "ord_uv": str(price) if price else "",
@@ -272,10 +304,10 @@ def credit_sell(code: str, qty: int, price: int, order_type: str, stex: str, con
272
304
  @click.argument("code")
273
305
  @click.argument("qty", type=int)
274
306
  @click.argument("price", type=int)
275
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
307
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
276
308
  @click.option("--cond-price", "mdfy_cond_uv", type=int, default=0, help="정정 조건부가격")
277
309
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
278
- def credit_modify(orig_order_no: str, code: str, qty: int, price: int, stex: str, mdfy_cond_uv: int, confirm: bool):
310
+ def credit_modify(orig_order_no: str, code: str, qty: int, price: int, dmst_stex_tp: str, mdfy_cond_uv: int, confirm: bool):
279
311
  """신용 정정주문 (kt10008).
280
312
 
281
313
  예: kiwoom order credit modify 0000139 005930 1 70000 --confirm
@@ -283,11 +315,11 @@ def credit_modify(orig_order_no: str, code: str, qty: int, price: int, stex: str
283
315
  if not confirm:
284
316
  click.confirm("주문을 실행하시겠습니까?", abort=True)
285
317
 
286
- console.print(f"[yellow]신용 정정 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty} 가격={price:,}")
318
+ _show_modify_preview("신용 정정", orig_order_no, code, qty, price, dmst_stex_tp)
287
319
 
288
320
  with KiwoomClient() as c:
289
321
  data, _ = c.request("kt10008", {
290
- "dmst_stex_tp": stex,
322
+ "dmst_stex_tp": dmst_stex_tp,
291
323
  "orig_ord_no": orig_order_no,
292
324
  "stk_cd": code,
293
325
  "mdfy_qty": str(qty),
@@ -301,9 +333,9 @@ def credit_modify(orig_order_no: str, code: str, qty: int, price: int, stex: str
301
333
  @click.argument("orig_order_no")
302
334
  @click.argument("code")
303
335
  @click.option("--qty", type=int, default=0, help="취소수량 (0=전량취소)")
304
- @click.option("--exchange", "stex", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
336
+ @click.option("--exchange", "dmst_stex_tp", default="KRX", type=click.Choice(["KRX", "NXT", "SOR"]), help="거래소")
305
337
  @click.option("--confirm", is_flag=True, help="확인 프롬프트 없이 주문 실행")
306
- def credit_cancel(orig_order_no: str, code: str, qty: int, stex: str, confirm: bool):
338
+ def credit_cancel(orig_order_no: str, code: str, qty: int, dmst_stex_tp: str, confirm: bool):
307
339
  """신용 취소주문 (kt10009).
308
340
 
309
341
  예: kiwoom order credit cancel 0000140 005930 --confirm
@@ -311,11 +343,11 @@ def credit_cancel(orig_order_no: str, code: str, qty: int, stex: str, confirm: b
311
343
  if not confirm:
312
344
  click.confirm("주문을 실행하시겠습니까?", abort=True)
313
345
 
314
- console.print(f"[yellow]신용 취소 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty or '전량'}")
346
+ _show_cancel_preview("신용 취소", orig_order_no, code, qty, dmst_stex_tp)
315
347
 
316
348
  with KiwoomClient() as c:
317
349
  data, _ = c.request("kt10009", {
318
- "dmst_stex_tp": stex,
350
+ "dmst_stex_tp": dmst_stex_tp,
319
351
  "orig_ord_no": orig_order_no,
320
352
  "stk_cd": code,
321
353
  "cncl_qty": str(qty),
@@ -399,7 +431,7 @@ def gold_modify(orig_order_no: str, code: str, qty: int, price: int, confirm: bo
399
431
  if not confirm:
400
432
  click.confirm("주문을 실행하시겠습니까?", abort=True)
401
433
 
402
- console.print(f"[yellow]금현물 정정 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty} 가격={price:,}")
434
+ _show_modify_preview("금현물 정정", orig_order_no, code, qty, price)
403
435
 
404
436
  with KiwoomClient() as c:
405
437
  data, _ = c.request("kt50002", {
@@ -424,7 +456,7 @@ def gold_cancel(orig_order_no: str, code: str, qty: int, confirm: bool):
424
456
  if not confirm:
425
457
  click.confirm("주문을 실행하시겠습니까?", abort=True)
426
458
 
427
- console.print(f"[yellow]금현물 취소 주문:[/] 원주문번호={orig_order_no} 종목={code} 수량={qty or '전량'}")
459
+ _show_cancel_preview("금현물 취소", orig_order_no, code, qty)
428
460
 
429
461
  with KiwoomClient() as c:
430
462
  data, _ = c.request("kt50003", {
@@ -16,6 +16,8 @@ Environment variables (non-sensitive only):
16
16
  Config file: ~/.kiwoom/config.toml
17
17
  """
18
18
 
19
+ # Comment
20
+
19
21
  from __future__ import annotations
20
22
 
21
23
  import os
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiwoom-cli
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: User-friendly CLI for the Kiwoom Securities REST API (키움증권)
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/gejyn14/kiwoom-cli
@@ -27,6 +27,7 @@ kiwoom_cli/commands/stock.py
27
27
  kiwoom_cli/commands/stream.py
28
28
  kiwoom_cli/commands/watch.py
29
29
  tests/test_account.py
30
+ tests/test_auth.py
30
31
  tests/test_cli.py
31
32
  tests/test_client.py
32
33
  tests/test_formatters.py
@@ -0,0 +1,30 @@
1
+ """Tests for kiwoom_cli.auth — keyring-backed token storage."""
2
+
3
+ import keyring
4
+
5
+ from kiwoom_cli import auth
6
+
7
+
8
+ def test_save_token_stores_in_keyring():
9
+ auth.save_token("abc123", profile="test-profile")
10
+ assert keyring.get_password(auth.KEYRING_SERVICE, "test-profile:token") == "abc123"
11
+
12
+
13
+ def test_load_token_returns_stored_value():
14
+ auth.save_token("xyz789", profile="test-profile")
15
+ assert auth.load_token(profile="test-profile") == "xyz789"
16
+
17
+
18
+ def test_load_token_returns_none_when_missing():
19
+ assert auth.load_token(profile="nonexistent") is None
20
+
21
+
22
+ def test_delete_token_removes_stored_value():
23
+ auth.save_token("to-delete", profile="test-profile")
24
+ assert auth.load_token(profile="test-profile") == "to-delete"
25
+ auth.delete_token(profile="test-profile")
26
+ assert auth.load_token(profile="test-profile") is None
27
+
28
+
29
+ def test_delete_token_missing_key_is_noop():
30
+ auth.delete_token(profile="never-existed") # must not raise
@@ -4,6 +4,7 @@
4
4
  import httpx
5
5
  import pytest
6
6
 
7
+ from kiwoom_cli import client as client_mod
7
8
  from kiwoom_cli.client import KiwoomClient, KiwoomAPIError
8
9
 
9
10
 
@@ -89,3 +90,68 @@ def test_request_all_paginates(mock_client):
89
90
  assert len(results) == 2
90
91
  assert results[0]["page"] == 1
91
92
  assert results[1]["page"] == 2
93
+
94
+
95
+ def test_request_all_respects_max_pages(mock_client):
96
+ client, httpx_mock = mock_client
97
+ for i in range(2):
98
+ httpx_mock.add_response(
99
+ url="https://mock.test/api/dostk/stkinfo",
100
+ json={"page": i, "return_code": 0},
101
+ headers={"cont-yn": "Y", "next-key": f"key{i+1}"},
102
+ )
103
+ results = client.request_all("ka10001", {"stk_cd": "005930"}, max_pages=2)
104
+ assert len(results) == 2
105
+ assert results[0]["page"] == 0
106
+ assert results[1]["page"] == 1
107
+
108
+
109
+ def test_client_init_loads_from_config_and_auth(monkeypatch):
110
+ monkeypatch.setattr(client_mod.config, "get_domain", lambda profile=None: "https://mock.test")
111
+ monkeypatch.setattr(client_mod.auth, "load_token", lambda profile=None: "stored-token")
112
+ c = client_mod.KiwoomClient()
113
+ try:
114
+ assert c.domain == "https://mock.test"
115
+ assert c.token == "stored-token"
116
+ finally:
117
+ c.close()
118
+
119
+
120
+ def test_issue_token_saves_and_returns(mock_client, monkeypatch):
121
+ client, httpx_mock = mock_client
122
+ saved = {}
123
+ monkeypatch.setattr(
124
+ client_mod.auth, "save_token",
125
+ lambda t, profile=None: saved.update({"token": t, "profile": profile}),
126
+ )
127
+ httpx_mock.add_response(
128
+ url="https://mock.test/oauth2/token",
129
+ json={"token": "new-token-xyz", "return_code": 0},
130
+ )
131
+ token = client.issue_token(appkey="ak123", secretkey="sk456")
132
+ assert token == "new-token-xyz"
133
+ assert client.token == "new-token-xyz"
134
+ assert saved["token"] == "new-token-xyz"
135
+ req = httpx_mock.get_request()
136
+ body = req.content.decode()
137
+ assert "ak123" in body and "sk456" in body
138
+ assert "client_credentials" in body
139
+
140
+
141
+ def test_revoke_token_clears_state(mock_client, monkeypatch):
142
+ client, httpx_mock = mock_client
143
+ monkeypatch.setattr(client_mod.config, "get_appkey", lambda profile=None: "ak123")
144
+ monkeypatch.setattr(client_mod.config, "get_secretkey", lambda profile=None: "sk456")
145
+ deleted = {}
146
+ monkeypatch.setattr(
147
+ client_mod.auth, "delete_token",
148
+ lambda profile=None: deleted.update({"called": True, "profile": profile}),
149
+ )
150
+ httpx_mock.add_response(
151
+ url="https://mock.test/oauth2/revoke",
152
+ json={"return_code": 0},
153
+ )
154
+ client.revoke_token()
155
+ assert client.token is None
156
+ assert deleted["called"] is True
157
+ assert deleted["profile"] is None
File without changes
File without changes
File without changes
File without changes
File without changes