Skip to content

Commit 19732fc

Browse files
author
Jeff Schroeder
committed
Updating pyth classes to use dataclass
This makes the code easier to read and makes the comparison less janky. * Updating PythPriceInfo to use datclass and flake8 updates * Updating PythPriceComponent to use a datclass * Updating PythPriceAccount tests to use dataclass * Update the binary bits to use base64 encoded bytes for brevity Nice review suggestion from @tony-ricciardi. Not yet using dataclass with PythProductAccount as there is some magic with the _prices attribute and the properties without full coverage yet. The tests were janky and I'll figure it out later when adding coverage for the various update bits.
1 parent 5498d52 commit 19732fc

File tree

6 files changed

+125
-230
lines changed

6 files changed

+125
-230
lines changed

pythclient/pythaccounts.py

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import annotations
2-
from typing import List, Dict, Tuple, Optional, Any
2+
from typing import List, Dict, Tuple, Optional, Any, ClassVar
33
import base64
44
from enum import Enum
5+
from dataclasses import dataclass, field
56
import struct
67

78
from loguru import logger
@@ -205,7 +206,6 @@ class PythProductAccount(PythAccount):
205206
first_price_account_key (SolanaPublicKey): the public key of the first price account (the price accounts form a linked list)
206207
attrs (dict): a dictionary of metadata attributes
207208
"""
208-
209209
def __init__(self, key: SolanaPublicKey, solana: SolanaClient) -> None:
210210
super().__init__(key, solana)
211211
self._prices: Optional[Dict[PythPriceType, PythPriceAccount]] = None
@@ -229,7 +229,6 @@ def symbol(self) -> str:
229229
"""
230230
Gets this account's symbol, or 'Unknown' if there is no 'symbol' attribute.
231231
"""
232-
233232
return self.attrs.get("symbol", "Unknown")
234233

235234
async def get_prices(self) -> Dict[PythPriceType, PythPriceAccount]:
@@ -258,7 +257,10 @@ async def refresh_prices(self) -> Dict[PythPriceType, PythPriceAccount]:
258257
self._prices = prices
259258
return prices
260259

261-
async def check_price_changes(self, update_accounts: bool = True) -> Tuple[List[PythPriceAccount], List[PythPriceAccount]]:
260+
async def check_price_changes(
261+
self,
262+
update_accounts: bool = True
263+
) -> Tuple[List[PythPriceAccount], List[PythPriceAccount]]:
262264
"""
263265
Checks for changes to the list of price accounts of this product.
264266
@@ -351,6 +353,7 @@ def __iter__(self):
351353
yield key, val
352354

353355

356+
@dataclass
354357
class PythPriceInfo:
355358
"""
356359
Contains price information.
@@ -365,15 +368,18 @@ class PythPriceInfo:
365368
exponent (int): the power-of-10 order of the price
366369
"""
367370

368-
LENGTH = 32
371+
LENGTH: ClassVar[int] = 32
369372

370-
def __init__(self, raw_price: int, raw_confidence_interval: int, price_status: PythPriceStatus, slot: int, exponent: int) -> None:
371-
self.raw_price = raw_price
372-
self.raw_confidence_interval = raw_confidence_interval
373-
self.price_status = price_status
374-
self.slot = slot
375-
self.exponent = exponent
373+
raw_price: int
374+
raw_confidence_interval: int
375+
price_status: PythPriceStatus
376+
slot: int
377+
exponent: int
376378

379+
price: float = field(init=False)
380+
confidence_interval: float = field(init=False)
381+
382+
def __post_init__(self):
377383
self.price = self.raw_price * (10 ** self.exponent)
378384
self.confidence_interval = self.raw_confidence_interval * \
379385
(10 ** self.exponent)
@@ -401,11 +407,8 @@ def __str__(self) -> str:
401407
def __repr__(self) -> str:
402408
return str(self)
403409

404-
def __iter__(self):
405-
for key, val in self.__dict__.items():
406-
yield key, val
407-
408410

411+
@dataclass
409412
class PythPriceComponent:
410413
"""
411414
Represents a price component. This is the individual prices each
@@ -421,13 +424,12 @@ class PythPriceComponent:
421424
in this price component
422425
"""
423426

424-
LENGTH = SolanaPublicKey.LENGTH + 2 * PythPriceInfo.LENGTH
427+
LENGTH: ClassVar[int] = SolanaPublicKey.LENGTH + 2 * PythPriceInfo.LENGTH
425428

426-
def __init__(self, publisher_key: SolanaPublicKey, last_aggregate_price_info: PythPriceInfo, latest_price_info: PythPriceInfo, exponent: int) -> None:
427-
self.publisher_key = publisher_key
428-
self.last_aggregate_price_info = last_aggregate_price_info
429-
self.latest_price_info = latest_price_info
430-
self.exponent = exponent
429+
publisher_key: SolanaPublicKey
430+
last_aggregate_price_info: PythPriceInfo
431+
latest_price_info: PythPriceInfo
432+
exponent: int
431433

432434
@staticmethod
433435
def deserialise(buffer: bytes, offset: int = 0, *, exponent: int) -> Optional[PythPriceComponent]:
@@ -449,12 +451,6 @@ def deserialise(buffer: bytes, offset: int = 0, *, exponent: int) -> Optional[Py
449451
latest_price = PythPriceInfo.deserialise(buffer, offset, exponent=exponent)
450452
return PythPriceComponent(key, last_aggregate_price, latest_price, exponent)
451453

452-
def __iter__(self):
453-
for key, val in self.__dict__.items():
454-
if isinstance(val, PythPriceInfo):
455-
val = dict(val)
456-
yield key, val
457-
458454

459455
class PythPriceAccount(PythAccount):
460456
"""
@@ -528,22 +524,22 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
528524
"""
529525
if version == _VERSION_2:
530526
price_type, exponent, num_components = struct.unpack_from("<IiI", buffer, offset)
531-
offset += 16 # struct.calcsize("IiII") (last I is the number of quoters that make up the aggregate)
527+
offset += 16 # struct.calcsize("IiII") (last I is the number of quoters that make up the aggregate)
532528
last_slot, valid_slot = struct.unpack_from("<QQ", buffer, offset)
533-
offset += 16 # QQ
529+
offset += 16 # QQ
534530
derivations = list(struct.unpack_from("<6q", buffer, offset))
535531
self.derivations = dict((type_, derivations[type_.value - 1]) for type_ in [TwEmaType.TWACVALUE, TwEmaType.TWAPVALUE])
536-
offset += 48 # 6q
532+
offset += 48 # 6q
537533
# All drv*_ fields sans min_publishers are currently unused
538534
_, min_publishers = struct.unpack_from("<qQ", buffer, offset)
539-
offset += 16 # <qQ
535+
offset += 16 # <qQ
540536
product_account_key_bytes, next_price_account_key_bytes = struct.unpack_from("32s32s", buffer, offset)
541-
offset += 96 # 32s32s32s
537+
offset += 96 # 32s32s32s
542538
elif version == _VERSION_1:
543539
price_type, exponent, num_components, _, last_slot, valid_slot, product_account_key_bytes, next_price_account_key_bytes, aggregator_key_bytes = struct.unpack_from(
544540
"<IiIIQQ32s32s32s", buffer, offset)
545541
self.derivations = {}
546-
offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
542+
offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
547543
else:
548544
assert False
549545

tests/test_mapping_account.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import pytest
23

34
from pythclient.pythaccounts import PythMappingAccount, _VERSION_2
@@ -20,23 +21,14 @@ def mapping_account_bytes():
2021
2122
Render those into a pasteable form with:
2223
23-
print(list(product_account_bytes))
24+
print(base64.b6encode(product_account_bytes))
2425
2526
"""
26-
return bytes(
27-
[
28-
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
29-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
30-
72, 214, 3, 61, 115, 62, 39, 149, 12, 46, 3, 81, 226, 80,
31-
84, 145, 205, 145, 84, 130, 79, 113, 109, 149, 19, 81, 76,
32-
116, 185, 249, 143, 88, 200, 12, 11, 20, 138, 186, 153, 75,
33-
37, 102, 234, 84, 167, 143, 240, 142, 250, 61, 94, 14, 195,
34-
208, 206, 234, 12, 250, 160, 63, 236, 37, 238, 162, 53, 21,
35-
179, 134, 30, 143, 233, 62, 95, 84, 11, 164, 7, 124, 33,
36-
100, 4, 120, 43, 134, 213, 231, 128, 119, 179, 203, 253,
37-
39, 49, 58, 179, 188
38-
]
39-
)
27+
return base64.b64decode((
28+
b'AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEjWAz1zPieVDC4DUeJQVJHNkVSCT3FtlRNRT'
29+
b'HS5+Y9YyAwLFIq6mUslZupUp4/wjvo9Xg7D0M7qDPqgP+wl7qI1FbOGHo/pPl9UC6QHfCFkBHgrhtXngHezy/0nMT'
30+
b'qzvA=='
31+
))
4032

4133

4234
@pytest.fixture

0 commit comments

Comments
 (0)