Skip to content

Commit 8519e4f

Browse files
committed
feat: support for ape 0.7, silverback 0.3, and pydantic 2
1 parent b9db3fb commit 8519e4f

File tree

5 files changed

+50
-38
lines changed

5 files changed

+50
-38
lines changed

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ packages = [
1111
]
1212

1313
[tool.poetry.dependencies]
14-
python = ">=3.8,<4"
15-
eth-ape = "^0.6.21"
16-
pydantic = "^1.10.13"
17-
silverback = ">=0.1.0,<0.3"
14+
python = ">=3.8.1,<4"
15+
eth-ape = "^0.7.3"
16+
silverback = "^0.3"
1817

1918
[tool.poetry.group.test.dependencies]
20-
ape-foundry = "^0.6.16"
19+
ape-foundry = "^0.7.1"
2120

2221
[tool.poetry.group.dev.dependencies]
2322
black = "^23.3.0"
23+
isort = "^5.13.2"
24+
mypy = "^1.8.0"
2425

2526
[build-system]
2627
requires = ["poetry-core"]

sdk/py/apepay/__init__.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
import json
21
import importlib
2+
import json
33
from datetime import datetime, timedelta
44
from decimal import Decimal
55
from functools import partial
6-
from typing import Any, Dict, Iterable, Iterator, List, Optional, Union, cast, ClassVar
6+
from pathlib import Path
7+
from typing import Any, ClassVar, Dict, Iterable, Iterator, List, Optional, Union, cast
78

89
from ape.api import ReceiptAPI
910
from ape.contracts.base import ContractInstance, ContractTransactionHandler
1011
from ape.exceptions import (
1112
CompilerError,
1213
ContractLogicError,
14+
ContractNotFoundError,
1315
DecodingError,
1416
ProjectError,
15-
ContractNotFoundError,
1617
)
17-
from ape.types import AddressType, ContractLog, HexBytes
18+
from ape.types import AddressType, ContractLog
1819
from ape.utils import BaseInterfaceModel, cached_property
1920
from ethpm_types import ContractType, PackageManifest
20-
from pydantic import ValidationError, validator
21+
from hexbytes import HexBytes
22+
from pydantic import ValidationError, ValidationInfo, field_validator
2123

2224
from .exceptions import (
2325
FundsNotClaimable,
@@ -48,7 +50,7 @@ def __eq__(self, other: Any) -> bool:
4850
# Try __eq__ from the other side.
4951
return NotImplemented
5052

51-
def validate(self, creator, token, amount_per_second, reason) -> bool:
53+
def validate(self, creator, token, amount_per_second, reason) -> bool: # type: ignore
5254
try:
5355
self.contract.validate.call(creator, token, amount_per_second, reason)
5456
return True
@@ -63,14 +65,16 @@ def validate(self, creator, token, amount_per_second, reason) -> bool:
6365
class StreamManager(BaseInterfaceModel):
6466
address: AddressType
6567
contract_type: Optional[ContractType] = None
66-
_local_contracts: ClassVar[Dict[str, ContractType]]
68+
_local_contracts: ClassVar[Dict[str, ContractType]] = dict()
6769

68-
@validator("address", pre=True)
70+
@field_validator("address", mode="before")
71+
@classmethod
6972
def normalize_address(cls, value: Any) -> AddressType:
7073
return cls.conversion_manager.convert(value, AddressType)
7174

72-
@validator("contract_type", pre=True, always=True)
73-
def fetch_contract_type(cls, value: Any, values: Dict[str, Any]) -> ContractType:
75+
@field_validator("contract_type", mode="before")
76+
@classmethod
77+
def fetch_contract_type(cls, value: Any, info: ValidationInfo) -> Optional[ContractType]:
7478
# 0. If pre-loaded, default to that type
7579
if value:
7680
return value
@@ -86,19 +90,24 @@ def fetch_contract_type(cls, value: Any, values: Dict[str, Any]) -> ContractType
8690

8791
# 2. If contract cache has it, use that
8892
try:
89-
if values.get("address") and (
90-
contract_type := cls.chain_manager.contracts.get(values["address"])
93+
if info.data.get("address") and (
94+
contract_type := cls.chain_manager.contracts.get(info.data["address"])
9195
):
9296
return contract_type
9397

9498
except Exception:
9599
pass
96100

97101
# 3. Most expensive way is through package resources
98-
cls._local_contracts = PackageManifest.parse_file(
99-
importlib.resources.files("apepay") / "manifest.json"
100-
).contract_types
101-
return cls._local_contracts["StreamManager"]
102+
manifest_file = Path(__file__).parent / "manifest.json"
103+
manifest_text = manifest_file.read_text()
104+
manifest = PackageManifest.parse_raw(manifest_text)
105+
106+
if not manifest or not manifest.contract_types:
107+
raise ValueError("Invalid manifest")
108+
109+
cls._local_contracts = manifest.contract_types
110+
return cls._local_contracts.get("StreamManager")
102111

103112
@property
104113
def contract(self) -> ContractInstance:
@@ -157,7 +166,7 @@ def set_validators(
157166

158167
def add_validators(
159168
self,
160-
*new_validators: Iterable[_ValidatorItem],
169+
*new_validators: _ValidatorItem,
161170
**txn_kwargs,
162171
) -> ReceiptAPI:
163172
return self.set_validators(
@@ -167,7 +176,7 @@ def add_validators(
167176

168177
def remove_validators(
169178
self,
170-
*validators: Iterable[_ValidatorItem],
179+
*validators: _ValidatorItem,
171180
**txn_kwargs,
172181
) -> ReceiptAPI:
173182
return self.set_validators(
@@ -307,14 +316,16 @@ class Stream(BaseInterfaceModel):
307316
creation_receipt: Optional[ReceiptAPI] = None
308317
transaction_hash: Optional[HexBytes] = None
309318

310-
@validator("transaction_hash", pre=True)
319+
@field_validator("transaction_hash", mode="before")
320+
@classmethod
311321
def normalize_transaction_hash(cls, value: Any) -> Optional[HexBytes]:
312322
if value:
313323
return HexBytes(cls.conversion_manager.convert(value, bytes))
314324

315325
return value
316326

317-
@validator("creator", pre=True)
327+
@field_validator("creator", mode="before")
328+
@classmethod
318329
def validate_addresses(cls, value):
319330
return (
320331
value if isinstance(value, str) else cls.conversion_manager.convert(value, AddressType)

sdk/py/apepay/daemon.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
from enum import Enum
55

66
import click
7-
from ape.types import AddressType
87
from apepay import Stream, StreamManager
9-
from silverback import SilverbackApp
8+
from eth_utils import to_checksum_address
9+
from silverback import SilverbackApp, SilverbackStartupState
1010

1111
from .settings import Settings
1212

@@ -35,8 +35,9 @@ def from_time_left(cls, time_left: timedelta) -> "Status":
3535

3636

3737
SM = StreamManager(
38-
address=os.environ.get("APEPAY_CONTRACT_ADDRESS")
39-
or click.prompt("What address to use?", type=AddressType)
38+
address=to_checksum_address(
39+
os.environ.get("APEPAY_CONTRACT_ADDRESS") or click.prompt("What address to use?", type=str)
40+
)
4041
)
4142

4243
app = SilverbackApp()
@@ -63,12 +64,12 @@ async def create_task_by_status(stream: Stream):
6364

6465

6566
@app.on_startup()
66-
async def app_started(state):
67+
async def app_started(startup_state: SilverbackStartupState):
6768
return await asyncio.gather(
6869
# Start watching all active streams and claim any completed but unclaimed streams
6970
*(
7071
create_task_by_status(stream)
71-
for stream in SM.all_streams()
72+
for stream in SM.all_streams(startup_state.last_block_seen)
7273
if stream.is_active or stream.amount_unlocked > 0
7374
)
7475
)

sdk/py/apepay/settings.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
from typing import Any
33

44
from apepay.utils import time_unit_to_timedelta
5-
from pydantic import BaseSettings, validator
5+
from pydantic import field_validator
6+
from pydantic_settings import BaseSettings, SettingsConfigDict
67

78

89
class Settings(BaseSettings):
10+
model_config = SettingsConfigDict(env_prefix="APEPAY_", case_sensitive=True)
11+
912
WARNING_LEVEL: timedelta = timedelta(days=2)
1013
CRITICAL_LEVEL: timedelta = timedelta(hours=12)
1114

12-
@validator("WARNING_LEVEL", "CRITICAL_LEVEL", pre=True)
15+
@field_validator("WARNING_LEVEL", "CRITICAL_LEVEL", mode="before")
1316
def _normalize_timedelta(cls, value: Any) -> timedelta:
1417
if isinstance(value, timedelta):
1518
return value
@@ -27,7 +30,3 @@ def _normalize_timedelta(cls, value: Any) -> timedelta:
2730
else:
2831
multiplier, time_unit = value.split(" ")
2932
return int(multiplier) * time_unit_to_timedelta(time_unit)
30-
31-
class Config:
32-
env_prefix = "APEPAY_"
33-
case_sensitive = True

sdk/py/apepay/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def async_wrap_iter(it: Iterator) -> AsyncIterator:
88
"""Wrap blocking iterator into an asynchronous one"""
99
loop = asyncio.get_event_loop()
10-
q = asyncio.Queue(1)
10+
q: asyncio.Queue = asyncio.Queue(1)
1111
exception = None
1212
_END = object()
1313

0 commit comments

Comments
 (0)