Skip to content

Commit

Permalink
Release v1.2.2 (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidMkCb authored Apr 9, 2024
1 parent 36578b9 commit a2aa377
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 14 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [1.2.2] - 2024-APR-9

### Added
- Support for ClosePosition endpoint

### Changed
- Audience no longer included in JWT generation

## [1.2.1] - 2024-MAR-27

### Added
Expand Down
2 changes: 1 addition & 1 deletion coinbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.2.1"
__version__ = "1.2.2"
7 changes: 3 additions & 4 deletions coinbase/jwt_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from coinbase.constants import BASE_URL, REST_SERVICE, WS_SERVICE


def build_jwt(key_var, secret_var, service, uri=None) -> str:
def build_jwt(key_var, secret_var, uri=None) -> str:
"""
:meta private:
"""
Expand All @@ -28,7 +28,6 @@ def build_jwt(key_var, secret_var, service, uri=None) -> str:
"iss": "coinbase-cloud",
"nbf": int(time.time()),
"exp": int(time.time()) + 120,
"aud": [service],
}

if uri:
Expand Down Expand Up @@ -61,7 +60,7 @@ def build_rest_jwt(uri, key_var, secret_var) -> str:
- **key_var (str)** - The API key
- **secret_var (str)** - The API key secret
"""
return build_jwt(key_var, secret_var, REST_SERVICE, uri=uri)
return build_jwt(key_var, secret_var, uri=uri)


def build_ws_jwt(key_var, secret_var) -> str:
Expand All @@ -80,7 +79,7 @@ def build_ws_jwt(key_var, secret_var) -> str:
- **key_var (str)** - The API key
- **secret_var (str)** - The API key secret
"""
return build_jwt(key_var, secret_var, WS_SERVICE)
return build_jwt(key_var, secret_var)


def format_jwt_uri(method, path) -> str:
Expand Down
1 change: 1 addition & 0 deletions coinbase/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class RESTClient(RESTBase):
from .fees import get_transaction_summary
from .futures import (
cancel_pending_futures_sweep,
close_position,
get_futures_balance_summary,
get_futures_position,
list_futures_positions,
Expand Down
28 changes: 27 additions & 1 deletion coinbase/rest/futures.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
from typing import Any, Dict
from typing import Any, Dict, Optional

from coinbase.constants import API_PREFIX


def close_position(
self, client_order_id: str, product_id: str, size: Optional[str] = None, **kwargs
) -> Dict[str, Any]:
"""
**Close Position**
_________________
[POST] https://api.coinbase.com/api/v3/brokerage/orders/close_position
__________
**Description:**
Places an order to close any open positions for a specified ``product_id``.
__________
**Read more on the official documentation:** `Close Position
<https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_closeposition>`_
"""
endpoint = f"{API_PREFIX}/orders/close_position"
data = {"client_order_id": client_order_id, "product_id": product_id, "size": size}

return self.post(endpoint, data=data, **kwargs)


def get_futures_balance_summary(self, **kwargs) -> Dict[str, Any]:
"""
**Get Futures Balance Summary**
Expand Down
32 changes: 32 additions & 0 deletions tests/rest/test_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@


class FuturesTest(unittest.TestCase):
def test_close_position(self):
client = RESTClient(TEST_API_KEY, TEST_API_SECRET)

expected_response = {
"client_order_id": "client_order_id_1",
"product_id": "product_id_1",
}

with Mocker() as m:
m.request(
"POST",
"https://api.coinbase.com/api/v3/brokerage/orders/close_position",
json=expected_response,
)
closedOrder = client.close_position(
"client_order_id_1", "product_id_1", "100"
)

captured_request = m.request_history[0]
captured_json = captured_request.json()

self.assertEqual(captured_request.query, "")
self.assertEqual(
captured_json,
{
"client_order_id": "client_order_id_1",
"product_id": "product_id_1",
"size": "100",
},
)
self.assertEqual(closedOrder, expected_response)

def test_get_futures_balance_summary(self):
client = RESTClient(TEST_API_KEY, TEST_API_SECRET)

Expand Down
10 changes: 2 additions & 8 deletions tests/test_jwt_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,24 @@ def test_build_rest_jwt(self):
uri = jwt_generator.format_jwt_uri("GET", "/api/v3/brokerage/accounts")
result_jwt = jwt_generator.build_rest_jwt(uri, TEST_API_KEY, TEST_API_SECRET)

decoded_data = jwt.decode(
result_jwt, TEST_API_SECRET, algorithms=["ES256"], audience=[REST_SERVICE]
)
decoded_data = jwt.decode(result_jwt, TEST_API_SECRET, algorithms=["ES256"])
header_bytes = base64.urlsafe_b64decode(str(result_jwt.split(".")[0] + "=="))
decoded_header = json.loads(header_bytes.decode("utf-8"))

self.assertEqual(decoded_data["sub"], TEST_API_KEY)
self.assertEqual(decoded_data["iss"], "coinbase-cloud")
self.assertEqual(decoded_data["aud"], [REST_SERVICE])
self.assertEqual(decoded_data["uri"], uri)
self.assertEqual(decoded_header["kid"], TEST_API_KEY)

def test_build_ws_jwt(self):
result_jwt = jwt_generator.build_ws_jwt(TEST_API_KEY, TEST_API_SECRET)

decoded_data = jwt.decode(
result_jwt, TEST_API_SECRET, algorithms=["ES256"], audience=[WS_SERVICE]
)
decoded_data = jwt.decode(result_jwt, TEST_API_SECRET, algorithms=["ES256"])
header_bytes = base64.urlsafe_b64decode(str(result_jwt.split(".")[0] + "=="))
decoded_header = json.loads(header_bytes.decode("utf-8"))

self.assertEqual(decoded_data["sub"], TEST_API_KEY)
self.assertEqual(decoded_data["iss"], "coinbase-cloud")
self.assertEqual(decoded_data["aud"], [WS_SERVICE])
self.assertNotIn("uri", decoded_data)
self.assertEqual(decoded_header["kid"], TEST_API_KEY)

Expand Down

0 comments on commit a2aa377

Please sign in to comment.