Skip to content

Commit 1b200f9

Browse files
authored
Feature/to json (#2)
* Add to_json conversion for pyth client.
1 parent b973d25 commit 1b200f9

File tree

6 files changed

+132
-0
lines changed

6 files changed

+132
-0
lines changed

pythclient/pythaccounts.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
193193
def __str__(self) -> str:
194194
return f"PythMappingAccount ({self.key})"
195195

196+
def to_json(self):
197+
198+
return {
199+
'entries': [str(x) for x in self.entries],
200+
'next_account_key': str(self.next_account_key)
201+
}
202+
196203

197204
class PythProductAccount(PythAccount):
198205
"""
@@ -350,6 +357,12 @@ def __iter__(self):
350357
if not key.startswith('_'):
351358
yield key, val
352359

360+
def to_json(self):
361+
362+
return {
363+
'symbol': self.symbol,
364+
}
365+
353366

354367
@dataclass
355368
class PythPriceInfo:
@@ -405,6 +418,19 @@ def __str__(self) -> str:
405418
def __repr__(self) -> str:
406419
return str(self)
407420

421+
def to_json(self):
422+
423+
return {
424+
"price": self.price,
425+
"confidence_interval": self.confidence_interval,
426+
"price_status": self.price_status.name,
427+
"pub_slot": self.pub_slot,
428+
"exponent": self.exponent,
429+
"raw_confidence_interval": self.raw_confidence_interval,
430+
"raw_price": self.raw_price
431+
}
432+
433+
408434

409435
@dataclass
410436
class PythPriceComponent:
@@ -449,6 +475,15 @@ def deserialise(buffer: bytes, offset: int = 0, *, exponent: int) -> Optional[Py
449475
latest_price = PythPriceInfo.deserialise(buffer, offset, exponent=exponent)
450476
return PythPriceComponent(key, last_aggregate_price, latest_price, exponent)
451477

478+
def to_json(self):
479+
480+
return {
481+
"publisher_key": str(self.publisher_key),
482+
"last_aggregate_price_info": self.last_aggregate_price_info.to_json(),
483+
"latest_price_info": self.latest_price_info.to_json(),
484+
"exponent": self.exponent
485+
}
486+
452487

453488
class PythPriceAccount(PythAccount):
454489
"""
@@ -603,6 +638,25 @@ def __str__(self) -> str:
603638
else:
604639
return f"PythPriceAccount {self.price_type} ({self.key})"
605640

641+
def to_json(self):
642+
643+
return {
644+
"product": self.product.to_json() if self.product else None,
645+
"price_type": self.price_type.name,
646+
"exponent": self.exponent,
647+
"num_components": self.num_components,
648+
"last_slot": self.last_slot,
649+
"valid_slot": self.valid_slot,
650+
"product_account_key": str(self.product_account_key),
651+
"next_price_account_key": str(self.next_price_account_key),
652+
"aggregate_price_info": self.aggregate_price_info.to_json() if self.aggregate_price_info else None,
653+
"price_components": [x.to_json() for x in self.price_components],
654+
"derivations": {EmaType(x).name: self.derivations.get(x) for x in list(self.derivations.keys())},
655+
"min_publishers": self.min_publishers,
656+
"aggregate_price": self.aggregate_price,
657+
"aggregate_price_confidence_interval": self.aggregate_price_confidence_interval
658+
}
659+
606660

607661
_ACCOUNT_TYPE_TO_CLASS = {
608662
PythAccountType.MAPPING: PythMappingAccount,

tests/test_mapping_account.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,18 @@ def test_mapping_account_str(mapping_account, solana_client):
9090
actual = str(mapping_account)
9191
expected = f"PythMappingAccount ({mapping_account.key})"
9292
assert actual == expected
93+
94+
95+
def test_mapping_account_to_json(mapping_account, solana_client):
96+
97+
ignore_keys = {"key", "solana", "lamports", "slot"}
98+
must_contain_keys = set()
99+
100+
keys_json = set(mapping_account.to_json().keys())
101+
keys_orig = set(mapping_account.__dict__.keys())
102+
103+
# test for differences
104+
assert keys_orig - ignore_keys == keys_json - ignore_keys
105+
106+
# test for missing keys
107+
assert must_contain_keys.issubset(keys_json)

tests/test_price_account.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,22 @@ def test_price_account_get_aggregate_price_status_got_stale(
178178

179179
price_status = price_account.aggregate_price_status
180180
assert price_status == PythPriceStatus.UNKNOWN
181+
182+
def test_price_account_to_json(
183+
price_account_bytes: bytes, price_account: PythPriceAccount
184+
):
185+
price_account.update_from(buffer=price_account_bytes, version=2, offset=0)
186+
price_account.slot = price_account.aggregate_price_info.pub_slot
187+
188+
# attributes which are not part of to_json
189+
ignore_keys = {"aggregate_price", "aggregate_price_confidence_interval", "solana", "slot", "key", "lamports"}
190+
must_contain_keys = {"aggregate_price", "aggregate_price_confidence_interval"}
191+
192+
keys_json = set(price_account.to_json().keys())
193+
keys_orig = set(price_account.__dict__.keys())
194+
195+
# test for differences
196+
assert keys_orig - ignore_keys == keys_json - ignore_keys
197+
198+
# test for missing keys
199+
assert must_contain_keys.issubset(keys_json)

tests/test_price_component.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,18 @@ def test_deserialise_null_publisher_key(price_component: PythPriceComponent, pri
5454
bad_bytes = bytes(b'\x00' * SolanaPublicKey.LENGTH) + price_component_bytes[SolanaPublicKey.LENGTH:]
5555
actual = PythPriceComponent.deserialise(bad_bytes, exponent=price_component.exponent)
5656
assert actual is None
57+
58+
59+
def test_price_component_to_json(price_component: PythPriceComponent, price_component_bytes: bytes):
60+
61+
ignore_keys = set()
62+
must_contain_keys = set()
63+
64+
keys_json = set(price_component.to_json().keys())
65+
keys_orig = set(price_component.__dict__.keys())
66+
67+
# test for differences
68+
assert keys_orig - ignore_keys == keys_json - ignore_keys
69+
70+
# test for missing keys
71+
assert must_contain_keys.issubset(keys_json)

tests/test_price_info.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,17 @@ def test_price_info_str(price_info_trading):
101101
expected = "PythPriceInfo status PythPriceStatus.TRADING price 596.09162"
102102
assert str(price_info_trading) == expected
103103
assert repr(price_info_trading) == expected
104+
105+
def test_price_info_to_json(price_info_trading):
106+
107+
ignore_keys = set()
108+
must_contain_keys = set()
109+
110+
keys_json = set(price_info_trading.to_json().keys())
111+
keys_orig = set(price_info_trading.__dict__.keys())
112+
113+
# test for differences
114+
assert keys_orig - ignore_keys == keys_json - ignore_keys
115+
116+
# test for missing keys
117+
assert must_contain_keys.issubset(keys_json)

tests/test_product_account.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,18 @@ def test_symbol_property_unknown(product_account: PythProductAccount, solana_cli
126126
solana=solana_client,
127127
)
128128
assert actual.symbol == "Unknown"
129+
130+
131+
def test_product_account_to_json(product_account: PythProductAccount):
132+
# attributes which are not part of to_json
133+
ignore_keys = {"_prices", "attrs", "first_price_account_key", "key", "lamports", "slot", "solana", "symbol"}
134+
must_contain_keys = {"symbol"}
135+
136+
keys_json = set(product_account.to_json().keys())
137+
keys_orig = set(product_account.__dict__.keys())
138+
139+
# test for differences
140+
assert keys_orig - ignore_keys == keys_json - ignore_keys
141+
142+
# test for missing keys
143+
assert must_contain_keys.issubset(keys_json)

0 commit comments

Comments
 (0)