From 1c750d1814a4ab9ce79c6ff7dc95e0887bf3df11 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 11:38:19 +0200 Subject: [PATCH 01/10] Initial to_json --- pythclient/pythaccounts.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 44232bd..27f1732 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -352,6 +352,13 @@ def __iter__(self): if not key.startswith('_'): yield key, val + def to_json(self): + + return { + 'prices': self.prices, + 'symbol': self.symbol, + } + @dataclass class PythPriceInfo: @@ -407,6 +414,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, + "slot": self.slot, + "exponent": self.exponent, + "raw_confidence_interval": self.raw_confidence_interval, + "raw_price": self.raw_price + } + + @dataclass class PythPriceComponent: @@ -451,6 +471,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": self.publisher_key, + "last_aggregate_price_info": self.last_aggregate_price_info, + "latest_price_info": self.latest_price_info, + "exponent": self.exponent + } + class PythPriceAccount(PythAccount): """ @@ -577,6 +606,25 @@ def __str__(self) -> str: else: return f"PythPriceAccount {self.price_type} ({self.key})" + def to_json(self): + + return { + "product": self.product, + "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": self.product_account_key, + "next_price_account_key": self.next_price_account_key, + "aggregate_price_info": self.aggregate_price_info, + "price_components": self.price_components, + "derivations": [TwEmaType(x).name 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, From 10503f371f4538979326dfce641910d6f5936663 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 12:25:59 +0200 Subject: [PATCH 02/10] Fix PythPriceAccount to_json. --- pythclient/pythaccounts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 27f1732..b023dae 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -609,7 +609,7 @@ def __str__(self) -> str: def to_json(self): return { - "product": self.product, + "product": self.product.to_json(), "price_type": self.price_type.name, "exponent": self.exponent, "num_components": self.num_components, @@ -617,8 +617,8 @@ def to_json(self): "valid_slot": self.valid_slot, "product_account_key": self.product_account_key, "next_price_account_key": self.next_price_account_key, - "aggregate_price_info": self.aggregate_price_info, - "price_components": self.price_components, + "aggregate_price_info": self.aggregate_price_info.to_json(), + "price_components": [x.to_json() for x in self.price_components], "derivations": [TwEmaType(x).name for x in list(self.derivations.keys())], "min_publishers": self.min_publishers, "aggregate_price": self.aggregate_price, From b9e34b6a5b97d14c6d217a017e1f83d1fdcf2bf2 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 12:40:55 +0200 Subject: [PATCH 03/10] Fix derivations and prices. --- pythclient/pythaccounts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index b023dae..b69cafb 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -355,7 +355,7 @@ def __iter__(self): def to_json(self): return { - 'prices': self.prices, + 'prices': {PythPriceType(x).name: self.prices.get(x).to_json() for x in self.prices.keys()}, 'symbol': self.symbol, } @@ -619,7 +619,7 @@ def to_json(self): "next_price_account_key": self.next_price_account_key, "aggregate_price_info": self.aggregate_price_info.to_json(), "price_components": [x.to_json() for x in self.price_components], - "derivations": [TwEmaType(x).name for x in list(self.derivations.keys())], + "derivations": {TwEmaType(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 From cdfee145291acc53f2afec47488a357c846005aa Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 12:47:14 +0200 Subject: [PATCH 04/10] Fix price type. --- pythclient/pythaccounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index b69cafb..2065473 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -355,7 +355,7 @@ def __iter__(self): def to_json(self): return { - 'prices': {PythPriceType(x).name: self.prices.get(x).to_json() for x in self.prices.keys()}, + 'price_types': [PythPriceType(x).name for x in self.prices.keys()], 'symbol': self.symbol, } From d8bb4e35ed9585bb9be390eafff66184e2b7ea70 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 12:51:43 +0200 Subject: [PATCH 05/10] Fix price info. --- pythclient/pythaccounts.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 2065473..1d9d18e 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -355,7 +355,6 @@ def __iter__(self): def to_json(self): return { - 'price_types': [PythPriceType(x).name for x in self.prices.keys()], 'symbol': self.symbol, } @@ -475,8 +474,8 @@ def to_json(self): return { "publisher_key": self.publisher_key, - "last_aggregate_price_info": self.last_aggregate_price_info, - "latest_price_info": self.latest_price_info, + "last_aggregate_price_info": self.last_aggregate_price_info.to_json(), + "latest_price_info": self.latest_price_info.to_json(), "exponent": self.exponent } From b485b42e5924ac23cf8415b1801cdc45b7e2182c Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 10 Feb 2022 15:19:54 +0200 Subject: [PATCH 06/10] Fix SolanaPublicKey to_json. --- pythclient/pythaccounts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 1d9d18e..2eba583 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -473,7 +473,7 @@ def deserialise(buffer: bytes, offset: int = 0, *, exponent: int) -> Optional[Py def to_json(self): return { - "publisher_key": self.publisher_key, + "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 @@ -614,8 +614,8 @@ def to_json(self): "num_components": self.num_components, "last_slot": self.last_slot, "valid_slot": self.valid_slot, - "product_account_key": self.product_account_key, - "next_price_account_key": self.next_price_account_key, + "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(), "price_components": [x.to_json() for x in self.price_components], "derivations": {TwEmaType(x).name: self.derivations.get(x) for x in list(self.derivations.keys())}, From 89dd41a849d0b4c2a6f81eb51efefb8800d9938a Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Fri, 18 Feb 2022 09:39:22 +0200 Subject: [PATCH 07/10] Add to_json to PythMappingAccount. --- pythclient/pythaccounts.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 2eba583..3f46821 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -195,6 +195,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): """ From 805c08f4f16735d7c883de653e00bd05c9a24566 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 24 Feb 2022 16:44:05 +0200 Subject: [PATCH 08/10] Fix PythPriceInfo slot to pub_slot. --- pythclient/pythaccounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index addaf91..55a8dfa 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -427,7 +427,7 @@ def to_json(self): "price": self.price, "confidence_interval": self.confidence_interval, "price_status": self.price_status.name, - "slot": self.slot, + "pub_slot": self.pub_slot, "exponent": self.exponent, "raw_confidence_interval": self.raw_confidence_interval, "raw_price": self.raw_price From 9ee2e32baa6bb48a9ce9e8a040beecd5f28111df Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 17 Mar 2022 11:12:15 +0200 Subject: [PATCH 09/10] Update to sync with fork. --- pythclient/pythaccounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 08a4f51..1c9145e 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -651,7 +651,7 @@ def to_json(self): "next_price_account_key": str(self.next_price_account_key), "aggregate_price_info": self.aggregate_price_info.to_json(), "price_components": [x.to_json() for x in self.price_components], - "derivations": {TwEmaType(x).name: self.derivations.get(x) for x in list(self.derivations.keys())}, + "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 From ae9fb82e02400d1a776813ed043df42009078f06 Mon Sep 17 00:00:00 2001 From: Tihomir Nedev Date: Thu, 17 Mar 2022 12:25:50 +0200 Subject: [PATCH 10/10] Add to_json tests. --- pythclient/pythaccounts.py | 4 ++-- tests/test_mapping_account.py | 15 +++++++++++++++ tests/test_price_account.py | 19 +++++++++++++++++++ tests/test_price_component.py | 15 +++++++++++++++ tests/test_price_info.py | 14 ++++++++++++++ tests/test_product_account.py | 15 +++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 1c9145e..e057139 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -641,7 +641,7 @@ def __str__(self) -> str: def to_json(self): return { - "product": self.product.to_json(), + "product": self.product.to_json() if self.product else None, "price_type": self.price_type.name, "exponent": self.exponent, "num_components": self.num_components, @@ -649,7 +649,7 @@ def to_json(self): "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(), + "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, 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)