From 2e222a46a0ef2d36204fd992cda7f8fa1873b5de Mon Sep 17 00:00:00 2001 From: gylim Date: Sun, 22 Mar 2026 21:51:26 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20orders=20API=EC=9D=98=20get=5Fchance=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=83=80=EC=9E=85=20=EB=B0=8F=20OrderCrea?= =?UTF-8?q?ted=20=EB=AA=A8=EB=8D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - get_chance()가 list[OrderChance] 대신 OrderChance를 반환하도록 수정 (Upbit API가 단일 객체를 반환하나 리스트로 처리하고 있었음) - OrderCreated 모델에서 remaining_volume, prevented_volume, prevented_locked을 optional로 변경 (시장가 주문 응답에 해당 필드가 포함되지 않음) - 동기/비동기 양쪽 모두 수정, mock 테스트도 함께 수정 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/upbeat/api/orders.py | 8 ++++---- src/upbeat/types/order.py | 6 +++--- tests/api/test_orders.py | 24 +++++++++++------------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/upbeat/api/orders.py b/src/upbeat/api/orders.py index 038924f..a700bfe 100644 --- a/src/upbeat/api/orders.py +++ b/src/upbeat/api/orders.py @@ -235,12 +235,12 @@ def cancel_and_new( ) return CancelAndNewOrderResponse.model_validate(response.data) - def get_chance(self, *, market: str) -> list[OrderChance]: + def get_chance(self, *, market: str) -> OrderChance: params = {"market": market} response = self._transport.request( "GET", "/v1/orders/chance", params=params, credentials=self._credentials ) - return [OrderChance.model_validate(item) for item in response.data] + return OrderChance.model_validate(response.data) class AsyncOrdersAPI(_AsyncAPIResource): @@ -458,9 +458,9 @@ async def cancel_and_new( ) return CancelAndNewOrderResponse.model_validate(response.data) - async def get_chance(self, *, market: str) -> list[OrderChance]: + async def get_chance(self, *, market: str) -> OrderChance: params = {"market": market} response = await self._transport.request( "GET", "/v1/orders/chance", params=params, credentials=self._credentials ) - return [OrderChance.model_validate(item) for item in response.data] + return OrderChance.model_validate(response.data) diff --git a/src/upbeat/types/order.py b/src/upbeat/types/order.py index 81375b8..1484cd4 100644 --- a/src/upbeat/types/order.py +++ b/src/upbeat/types/order.py @@ -32,7 +32,7 @@ class OrderCreated(BaseModel): state: str created_at: str volume: str | None = None - remaining_volume: str + remaining_volume: str | None = None executed_volume: str reserved_fee: str remaining_fee: str @@ -42,8 +42,8 @@ class OrderCreated(BaseModel): time_in_force: str | None = None identifier: str | None = None smp_type: str | None = None - prevented_volume: str - prevented_locked: str + prevented_volume: str | None = None + prevented_locked: str | None = None # ── OrderDetail (GET /v1/order 응답) ──────────────────────────────────── diff --git a/tests/api/test_orders.py b/tests/api/test_orders.py index 96935d9..e0cc632 100644 --- a/tests/api/test_orders.py +++ b/tests/api/test_orders.py @@ -512,27 +512,26 @@ def test_calls_correct_endpoint(self) -> None: def handler(request: httpx.Request) -> httpx.Response: assert request.url.path == "/v1/orders/chance" assert request.url.params["market"] == "KRW-BTC" - return _json_response([ORDER_CHANCE_DATA]) + return _json_response(ORDER_CHANCE_DATA) transport = _make_transport(handler) api = OrdersAPI(transport, CREDENTIALS) result = api.get_chance(market="KRW-BTC") - assert len(result) == 1 - assert isinstance(result[0], OrderChance) - assert result[0].bid_fee == "0.0005" + assert isinstance(result, OrderChance) + assert result.bid_fee == "0.0005" def test_parses_nested_objects(self) -> None: def handler(request: httpx.Request) -> httpx.Response: - return _json_response([ORDER_CHANCE_DATA]) + return _json_response(ORDER_CHANCE_DATA) transport = _make_transport(handler) api = OrdersAPI(transport, CREDENTIALS) result = api.get_chance(market="KRW-BTC") - assert result[0].market.id == "KRW-BTC" - assert result[0].market.bid is not None - assert result[0].market.bid.currency == "KRW" - assert result[0].bid_account.currency == "KRW" - assert result[0].ask_account.currency == "BTC" + assert result.market.id == "KRW-BTC" + assert result.market.bid is not None + assert result.market.bid.currency == "KRW" + assert result.bid_account.currency == "KRW" + assert result.ask_account.currency == "BTC" # ── TestAsyncOrders ────────────────────────────────────────────────────── @@ -578,10 +577,9 @@ async def handler(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio async def test_get_chance(self) -> None: async def handler(request: httpx.Request) -> httpx.Response: - return _json_response([ORDER_CHANCE_DATA]) + return _json_response(ORDER_CHANCE_DATA) transport = _make_async_transport(handler) api = AsyncOrdersAPI(transport, CREDENTIALS) result = await api.get_chance(market="KRW-BTC") - assert len(result) == 1 - assert isinstance(result[0], OrderChance) + assert isinstance(result, OrderChance)