λ³Έ λ¬Έμλ kiwoom_api_spec.json λ°μ΄ν°λ₯Ό μ½μ΄ 337κ°μ ν€μ REST API μμ²/μλ΅μ λμνλ νμ΄μ¬ μ½λ(Pydantic λͺ¨λΈ λ° ν΄λΌμ΄μΈνΈ λ©μλ)λ₯Ό μλμΌλ‘ μμ±νλ μ½λ μμ±κΈ°(Codegen)μ μ€κ³ λ° κ΅¬ν κ°μ΄λμ
λλ€.
μ½λ μμ±κΈ°λ κ°λ° λ¨κ³ λλ API μ€ν λ³κ²½ μ 1νμ±μΌλ‘ μ€νλλ λ 립ν μ€ν¬λ¦½νΈμ λλ€.
flowchart LR
Spec[kiwoom_api_spec.json] --> Parser[codegen.py νμ]
Parser -->|1. λͺ¨λΈ ν
νλ¦Ώ μ μ©| Models[kiwoom/models/..]
Parser -->|2. API ν¨μ ν
νλ¦Ώ μ μ©| APIs[kiwoom/api/..]
data/kiwoom_api_spec.json νμΌμ νμ±νμ¬ λ©λͺ¨λ¦¬μ λ‘λν©λλ€.category_large νλλ₯Ό κΈ°μ€μΌλ‘ κ΅λ΄μ£Όμ(domestic), λ―Έκ΅μ£Όμ(overseas), μΈμ¦(auth)μΌλ‘ λΆλ₯ν©λλ€.request.body λ° response.body λͺ
μΈλ₯Ό κΈ°λ°μΌλ‘ kiwoom/models/ ν΄λμ κ²μ¦ ν΄λμ€λ₯Ό μμ±ν©λλ€.kiwoom/api/ ν΄λμ λΉλκΈ° λ©μλλ₯Ό μμ±ν©λλ€.ν€μ API μ€νμ νμ μ νμ΄μ¬ λ° Pydantic νμμΌλ‘ λ§€νν©λλ€. ν€μ μ€νμ νλ μ€λͺ μ΄λ λ°μ΄ν° μμλ₯Ό μ°Έκ³ νμ¬ νμ ν©λλ€.
| ν€μ μ€ν νμ | νμ΄μ¬ νμ | λΉκ³ |
|---|---|---|
String |
str |
κΈ°λ³Έ λ¬Έμμ΄ |
Integer / Long |
int |
μ«μ |
Float / Double |
float |
μμμ |
Array / List |
list[NestedModel] |
μ€μ²© λ°μ΄ν° ꡬ쑰 |
null λλ λ―Έμ§μ |
str |
κΈ°λ³Έκ° strλ‘ μμ νκ² ν΄λ°± |
required κ°μ΄ "N"μΈ κ²½μ° Optional[T] = NoneμΌλ‘ λ§€νν©λλ€.fid_cond_mrkt_div_code λ± μ€λ€μ΄ν¬ μΌμ΄μ€(Snake Case) λλ μΉ΄λ© μΌμ΄μ€λ‘ λμ΄ μμ΅λλ€.import, class, type λ±)μ μΆ©λνλ κ²½μ° λ€μ μΈλλ°(_)λ₯Ό λΆμ¬ ννΌν©λλ€. (μ: type -> type_)Field(..., description="νκΈ μ€λͺ
")μ μ κ·Ή νμ©νμ¬ IDEμμ λ§μ°μ€ νΈλ² μ νκΈ ν΄νμ΄ λ¨λλ‘ μ λν©λλ€.κ°μ₯ μ§κ΄μ μ΄κ³ κ°λ²Όμ΄ νμ΄μ¬ f-string λλ Jinja2 ν νλ¦Ώμ μ¬μ©νμ¬ μ½λλ₯Ό μμ±ν©λλ€.
# {category}.py λͺ¨λΈ νμΌμ ν€λ
from pydantic import BaseModel, Field
from typing import Optional, List
# κ° TRμ λν΄ λ°λ³΅ μμ±λ ν΄λμ€ ν¬λ§·
class {ClassName}Request(BaseModel):
"""
{api_name} ({api_id}) μμ² λͺ¨λΈ
"""
{RequestFields}
class {ClassName}Response(BaseModel):
"""
{api_name} ({api_id}) μλ΅ λͺ¨λΈ
"""
{ResponseFields}
# api/{category}.py μ μ μλ λΉλκΈ° API ν΄λμ€μ λ©μλ ν¬λ§·
class {CategoryClassName}API:
async def {method_name}(self, request: {ClassName}Request) -> {ClassName}Response:
"""
{api_name} ({api_id})
URL: {url} (Method: {http_method})
"""
# API νΈμΆ μ μ‘ κ³΅ν΅ λ‘μ§ μ€ν
response_json = await self._request(
method="{http_method}",
path="{url}",
headers={headers_dict},
json=request.model_dump(exclude_none=True)
)
return {ClassName}Response.model_validate(response_json)
tools/codegen.py)μ€μ νλ‘μ νΈ λ£¨νΈ μλ tools/codegen.pyλ‘ μμ±ν νμ΄μ¬ μ€ν¬λ¦½νΈμ κΈ°λ³Έ λΌλμ
λλ€.
import json
from pathlib import Path
def load_spec():
with open("data/kiwoom_api_spec.json", encoding="utf-8") as f:
return json.load(f)
def clean_desc(desc):
if not desc:
return ""
return desc.replace("\n", " ").replace('"', '\\"')
def generate_models(spec):
# categoryλ³λ‘ λΆλ₯ν΄μ Pydantic λͺ¨λΈ μ½λ λ¬Έμμ΄μ μμ±νκ³ νμΌμ μ
print("Generating Pydantic models...")
# ... μμΈ λ£¨ν ꡬν
def generate_apis(spec):
# categoryλ³λ‘ λΆλ₯ν΄μ ν΄λΌμ΄μΈνΈ λ©μλ μ½λλ₯Ό μμ±νκ³ νμΌμ μ
print("Generating API endpoints...")
# ... μμΈ λ£¨ν ꡬν
def main():
spec = load_spec()
generate_models(spec)
generate_apis(spec)
print("β¨ Code Generation Completed!")
if __name__ == "__main__":
main()
μ½λ μμ±κΈ°κ° μμ±λλ©΄ μλ λͺ λ Ήμ΄λ₯Ό ν΅ν΄ μ½λλ₯Ό μ¦μ μ°μ΄λ΄κ³ νμ§ κ²μ¬κΉμ§ μΌκ΄ μνν©λλ€.
# 1. κ°μνκ²½ μ§μ
μνμμ μ½λ μμ± μ€ν¬λ¦½νΈ μ€ν
uv run python tools/codegen.py
# 2. μλ μμ±λ μ½λμ ν¬λ§·ν
λ° μ μ λΆμ μν
uv run ruff format kiwoom/
uv run ruff check kiwoom/ --fix
# 3. νμ
ννΈ μλ¬ κ²μ¬
uv run mypy kiwoom/