Skip to content

Commit 34bd2c2

Browse files
Releasing v0.21.0 (#117)
* Improve error message when SDK is used and not initialized (#114) * Update UserOperation status to support string comparison (#115) * Preparing v0.21.0 release (#116)
1 parent 792e518 commit 34bd2c2

11 files changed

Lines changed: 86 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
## [0.21.0] - 2025-02-28
6+
7+
### Added
8+
- Ability to check UserOperation status using a string comparison.
9+
510
## [0.20.0] - 2025-02-25
611

712
### Added

cdp/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.20.0"
1+
__version__ = "0.21.0"

cdp/cdp.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from cdp.api_clients import ApiClients
66
from cdp.cdp_api_client import CdpApiClient
77
from cdp.constants import SDK_DEFAULT_SOURCE
8-
from cdp.errors import InvalidConfigurationError
8+
from cdp.errors import InvalidConfigurationError, UninitializedSDKError
99

1010

1111
class Cdp:
@@ -18,7 +18,7 @@ class Cdp:
1818
debugging (bool): Whether debugging is enabled.
1919
base_path (str): The base URL for the Platform API.
2020
max_network_retries (int): The maximum number of network retries.
21-
api_clients (Optional[ApiClients]): The Platform API clients instance.
21+
api_clients: The Platform API clients instance.
2222
2323
"""
2424

@@ -30,7 +30,15 @@ class Cdp:
3030
debugging = False
3131
base_path = "https://api.cdp.coinbase.com/platform"
3232
max_network_retries = 3
33-
api_clients: ApiClients | None = None
33+
34+
class ApiClientsWrapper:
35+
"""Wrapper that raises a helpful error when SDK is not initialized."""
36+
37+
def __getattr__(self, _name):
38+
"""Raise an error when accessing an attribute of the ApiClientsWrapper."""
39+
raise UninitializedSDKError()
40+
41+
api_clients = ApiClientsWrapper()
3442

3543
def __new__(cls):
3644
"""Create or return the singleton instance of the Cdp class.

cdp/errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
from cdp.client.exceptions import ApiException
55

66

7+
class UninitializedSDKError(Exception):
8+
"""Exception raised when trying to access CDP API clients before SDK initialization."""
9+
10+
def __init__(self):
11+
message = (
12+
"Coinbase SDK has not been initialized. Please initialize by calling either:\n\n"
13+
+ "- Cdp.configure(api_key_name='...', private_key='...')\n"
14+
"- Cdp.configure_from_json(file_path='/path/to/api_keys.json')\n\n"
15+
"If needed, register for API keys at https://portal.cdp.coinbase.com/ or view the docs at https://docs.cdp.coinbase.com/wallet-api/docs/welcome"
16+
)
17+
super().__init__(message)
18+
19+
720
class ApiError(Exception):
821
"""A wrapper for API exceptions to provide more context."""
922

cdp/user_operation.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ def __repr__(self) -> str:
4141
"""Return a string representation of the Status."""
4242
return str(self)
4343

44+
def __eq__(self, other):
45+
"""Check if the status is equal to another object. Supports string comparison."""
46+
if isinstance(other, str):
47+
return self.value == other
48+
return super().__eq__(other)
49+
50+
def __hash__(self):
51+
"""Return a hash value for the enum member to allow use as dictionary keys."""
52+
return hash(self.name)
53+
4454
def __init__(self, model: UserOperationModel, smart_wallet_address: str) -> None:
4555
"""Initialize the UserOperation class.
4656

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
project = 'CDP SDK'
1616
author = 'Coinbase Developer Platform'
17-
release = '0.20.0'
17+
release = '0.21.0'
1818

1919
# -- General configuration ---------------------------------------------------
2020
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "cdp-sdk"
3-
version = "0.20.0"
3+
version = "0.21.0"
44
description = "CDP Python SDK"
55
authors = ["John Peterson <john.peterson@coinbase.com>"]
66
license = "LICENSE.md"

tests/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
11
import os
2+
from unittest.mock import MagicMock
3+
4+
import pytest
5+
6+
from cdp import Cdp
7+
from cdp.api_clients import ApiClients
8+
9+
10+
@pytest.fixture(autouse=True)
11+
def initialize_cdp(request):
12+
"""Initialize the CDP SDK with mock API clients before each test."""
13+
# Skip this fixture for e2e tests
14+
if request.node.get_closest_marker("e2e"):
15+
yield
16+
return
17+
18+
original_api_clients = Cdp.api_clients
19+
mock_api_clients = MagicMock(spec=ApiClients)
20+
Cdp.api_clients = mock_api_clients
21+
yield
22+
Cdp.api_clients = original_api_clients
23+
224

325
factory_modules = [
426
f[:-3] for f in os.listdir("./tests/factories") if f.endswith(".py") and f != "__init__.py"

tests/factories/api_key_factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def dummy_key_factory():
1212
- "ed25519-32": Returns a base64-encoded 32-byte Ed25519 private key.
1313
- "ed25519-64": Returns a base64-encoded 64-byte dummy Ed25519 key (the first 32 bytes will be used).
1414
"""
15+
1516
def _create_dummy(key_type: str = "ecdsa") -> str:
1617
if key_type == "ecdsa":
1718
return (
@@ -25,9 +26,10 @@ def _create_dummy(key_type: str = "ecdsa") -> str:
2526
return "BXyKC+eFINc/6ztE/3neSaPGgeiU9aDRpaDnAbaA/vyTrUNgtuh/1oX6Vp+OEObV3SLWF+OkF2EQNPtpl0pbfA=="
2627
elif key_type == "ed25519-64":
2728
# Create a 64-byte dummy by concatenating a 32-byte sequence with itself.
28-
dummy_32 = b'\x01' * 32
29+
dummy_32 = b"\x01" * 32
2930
dummy_64 = dummy_32 + dummy_32
3031
return base64.b64encode(dummy_64).decode("utf-8")
3132
else:
3233
raise ValueError("Unsupported key type for dummy key creation")
34+
3335
return _create_dummy

tests/test_api_key_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ def test_parse_private_key_pem_ec(dummy_key_factory):
1010
parsed_key = _parse_private_key(dummy_key)
1111
assert isinstance(parsed_key, ec.EllipticCurvePrivateKey)
1212

13+
1314
def test_parse_private_key_ed25519_32(dummy_key_factory):
1415
"""Test that a base64-encoded 32-byte Ed25519 key is parsed correctly using a dummy key from the factory."""
1516
dummy_key = dummy_key_factory("ed25519-32")
1617
parsed_key = _parse_private_key(dummy_key)
1718
assert isinstance(parsed_key, ed25519.Ed25519PrivateKey)
1819

20+
1921
def test_parse_private_key_ed25519_64(dummy_key_factory):
2022
"""Test that a base64-encoded 64-byte input is parsed correctly by taking the first 32 bytes using a dummy key from the factory."""
2123
dummy_key = dummy_key_factory("ed25519-64")
2224
parsed_key = _parse_private_key(dummy_key)
2325
assert isinstance(parsed_key, ed25519.Ed25519PrivateKey)
2426

27+
2528
def test_parse_private_key_invalid():
2629
"""Test that an invalid key string raises a ValueError."""
2730
with pytest.raises(ValueError, match="Could not parse the private key"):

0 commit comments

Comments
 (0)