diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 34b4944..e057139 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -193,6 +193,13 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None: def __str__(self) -> str: return f"PythMappingAccount ({self.key})" + def to_json(self): + + return { + 'entries': [str(x) for x in self.entries], + 'next_account_key': str(self.next_account_key) + } + class PythProductAccount(PythAccount): """ @@ -350,6 +357,12 @@ def __iter__(self): if not key.startswith('_'): yield key, val + def to_json(self): + + return { + 'symbol': self.symbol, + } + @dataclass class PythPriceInfo: @@ -405,6 +418,19 @@ def __str__(self) -> str: def __repr__(self) -> str: return str(self) + def to_json(self): + + return { + "price": self.price, + "confidence_interval": self.confidence_interval, + "price_status": self.price_status.name, + "pub_slot": self.pub_slot, + "exponent": self.exponent, + "raw_confidence_interval": self.raw_confidence_interval, + "raw_price": self.raw_price + } + + @dataclass class PythPriceComponent: @@ -449,6 +475,15 @@ def deserialise(buffer: bytes, offset: int = 0, *, exponent: int) -> Optional[Py latest_price = PythPriceInfo.deserialise(buffer, offset, exponent=exponent) return PythPriceComponent(key, last_aggregate_price, latest_price, exponent) + def to_json(self): + + return { + "publisher_key": str(self.publisher_key), + "last_aggregate_price_info": self.last_aggregate_price_info.to_json(), + "latest_price_info": self.latest_price_info.to_json(), + "exponent": self.exponent + } + class PythPriceAccount(PythAccount): """ @@ -603,6 +638,25 @@ def __str__(self) -> str: else: return f"PythPriceAccount {self.price_type} ({self.key})" + def to_json(self): + + return { + "product": self.product.to_json() if self.product else None, + "price_type": self.price_type.name, + "exponent": self.exponent, + "num_components": self.num_components, + "last_slot": self.last_slot, + "valid_slot": self.valid_slot, + "product_account_key": str(self.product_account_key), + "next_price_account_key": str(self.next_price_account_key), + "aggregate_price_info": self.aggregate_price_info.to_json() if self.aggregate_price_info else None, + "price_components": [x.to_json() for x in self.price_components], + "derivations": {EmaType(x).name: self.derivations.get(x) for x in list(self.derivations.keys())}, + "min_publishers": self.min_publishers, + "aggregate_price": self.aggregate_price, + "aggregate_price_confidence_interval": self.aggregate_price_confidence_interval + } + _ACCOUNT_TYPE_TO_CLASS = { PythAccountType.MAPPING: PythMappingAccount, diff --git a/tests/test_mapping_account.py b/tests/test_mapping_account.py index f384937..daf6765 100644 --- a/tests/test_mapping_account.py +++ b/tests/test_mapping_account.py @@ -90,3 +90,18 @@ def test_mapping_account_str(mapping_account, solana_client): actual = str(mapping_account) expected = f"PythMappingAccount ({mapping_account.key})" assert actual == expected + + +def test_mapping_account_to_json(mapping_account, solana_client): + + ignore_keys = {"key", "solana", "lamports", "slot"} + must_contain_keys = set() + + keys_json = set(mapping_account.to_json().keys()) + keys_orig = set(mapping_account.__dict__.keys()) + + # test for differences + assert keys_orig - ignore_keys == keys_json - ignore_keys + + # test for missing keys + assert must_contain_keys.issubset(keys_json) diff --git a/tests/test_price_account.py b/tests/test_price_account.py index 2ac3254..bfec29b 100644 --- a/tests/test_price_account.py +++ b/tests/test_price_account.py @@ -178,3 +178,22 @@ def test_price_account_get_aggregate_price_status_got_stale( price_status = price_account.aggregate_price_status assert price_status == PythPriceStatus.UNKNOWN + +def test_price_account_to_json( + price_account_bytes: bytes, price_account: PythPriceAccount +): + price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.slot = price_account.aggregate_price_info.pub_slot + + # attributes which are not part of to_json + ignore_keys = {"aggregate_price", "aggregate_price_confidence_interval", "solana", "slot", "key", "lamports"} + must_contain_keys = {"aggregate_price", "aggregate_price_confidence_interval"} + + keys_json = set(price_account.to_json().keys()) + keys_orig = set(price_account.__dict__.keys()) + + # test for differences + assert keys_orig - ignore_keys == keys_json - ignore_keys + + # test for missing keys + assert must_contain_keys.issubset(keys_json) diff --git a/tests/test_price_component.py b/tests/test_price_component.py index 77b1bca..715871f 100644 --- a/tests/test_price_component.py +++ b/tests/test_price_component.py @@ -54,3 +54,18 @@ def test_deserialise_null_publisher_key(price_component: PythPriceComponent, pri bad_bytes = bytes(b'\x00' * SolanaPublicKey.LENGTH) + price_component_bytes[SolanaPublicKey.LENGTH:] actual = PythPriceComponent.deserialise(bad_bytes, exponent=price_component.exponent) assert actual is None + + +def test_price_component_to_json(price_component: PythPriceComponent, price_component_bytes: bytes): + + ignore_keys = set() + must_contain_keys = set() + + keys_json = set(price_component.to_json().keys()) + keys_orig = set(price_component.__dict__.keys()) + + # test for differences + assert keys_orig - ignore_keys == keys_json - ignore_keys + + # test for missing keys + assert must_contain_keys.issubset(keys_json) diff --git a/tests/test_price_info.py b/tests/test_price_info.py index 7d7c08e..054441c 100644 --- a/tests/test_price_info.py +++ b/tests/test_price_info.py @@ -101,3 +101,17 @@ def test_price_info_str(price_info_trading): expected = "PythPriceInfo status PythPriceStatus.TRADING price 596.09162" assert str(price_info_trading) == expected assert repr(price_info_trading) == expected + +def test_price_info_to_json(price_info_trading): + + ignore_keys = set() + must_contain_keys = set() + + keys_json = set(price_info_trading.to_json().keys()) + keys_orig = set(price_info_trading.__dict__.keys()) + + # test for differences + assert keys_orig - ignore_keys == keys_json - ignore_keys + + # test for missing keys + assert must_contain_keys.issubset(keys_json) diff --git a/tests/test_product_account.py b/tests/test_product_account.py index 226a77b..e243c4e 100644 --- a/tests/test_product_account.py +++ b/tests/test_product_account.py @@ -126,3 +126,18 @@ def test_symbol_property_unknown(product_account: PythProductAccount, solana_cli solana=solana_client, ) assert actual.symbol == "Unknown" + + +def test_product_account_to_json(product_account: PythProductAccount): + # attributes which are not part of to_json + ignore_keys = {"_prices", "attrs", "first_price_account_key", "key", "lamports", "slot", "solana", "symbol"} + must_contain_keys = {"symbol"} + + keys_json = set(product_account.to_json().keys()) + keys_orig = set(product_account.__dict__.keys()) + + # test for differences + assert keys_orig - ignore_keys == keys_json - ignore_keys + + # test for missing keys + assert must_contain_keys.issubset(keys_json)