Skip to content

Commit

Permalink
feat: support for ape 0.7, silverback 0.3, and pydantic 2
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeshultz committed Jan 5, 2024
1 parent b9db3fb commit 8519e4f
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 38 deletions.
11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ packages = [
]

[tool.poetry.dependencies]
python = ">=3.8,<4"
eth-ape = "^0.6.21"
pydantic = "^1.10.13"
silverback = ">=0.1.0,<0.3"
python = ">=3.8.1,<4"
eth-ape = "^0.7.3"
silverback = "^0.3"

[tool.poetry.group.test.dependencies]
ape-foundry = "^0.6.16"
ape-foundry = "^0.7.1"

[tool.poetry.group.dev.dependencies]
black = "^23.3.0"
isort = "^5.13.2"
mypy = "^1.8.0"

[build-system]
requires = ["poetry-core"]
Expand Down
51 changes: 31 additions & 20 deletions sdk/py/apepay/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import json
import importlib
import json
from datetime import datetime, timedelta
from decimal import Decimal
from functools import partial
from typing import Any, Dict, Iterable, Iterator, List, Optional, Union, cast, ClassVar
from pathlib import Path
from typing import Any, ClassVar, Dict, Iterable, Iterator, List, Optional, Union, cast

from ape.api import ReceiptAPI
from ape.contracts.base import ContractInstance, ContractTransactionHandler
from ape.exceptions import (
CompilerError,
ContractLogicError,
ContractNotFoundError,
DecodingError,
ProjectError,
ContractNotFoundError,
)
from ape.types import AddressType, ContractLog, HexBytes
from ape.types import AddressType, ContractLog
from ape.utils import BaseInterfaceModel, cached_property
from ethpm_types import ContractType, PackageManifest
from pydantic import ValidationError, validator
from hexbytes import HexBytes
from pydantic import ValidationError, ValidationInfo, field_validator

from .exceptions import (
FundsNotClaimable,
Expand Down Expand Up @@ -48,7 +50,7 @@ def __eq__(self, other: Any) -> bool:
# Try __eq__ from the other side.
return NotImplemented

def validate(self, creator, token, amount_per_second, reason) -> bool:
def validate(self, creator, token, amount_per_second, reason) -> bool: # type: ignore
try:
self.contract.validate.call(creator, token, amount_per_second, reason)
return True
Expand All @@ -63,14 +65,16 @@ def validate(self, creator, token, amount_per_second, reason) -> bool:
class StreamManager(BaseInterfaceModel):
address: AddressType
contract_type: Optional[ContractType] = None
_local_contracts: ClassVar[Dict[str, ContractType]]
_local_contracts: ClassVar[Dict[str, ContractType]] = dict()

@validator("address", pre=True)
@field_validator("address", mode="before")
@classmethod
def normalize_address(cls, value: Any) -> AddressType:
return cls.conversion_manager.convert(value, AddressType)

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

# 2. If contract cache has it, use that
try:
if values.get("address") and (
contract_type := cls.chain_manager.contracts.get(values["address"])
if info.data.get("address") and (
contract_type := cls.chain_manager.contracts.get(info.data["address"])
):
return contract_type

except Exception:
pass

# 3. Most expensive way is through package resources
cls._local_contracts = PackageManifest.parse_file(
importlib.resources.files("apepay") / "manifest.json"
).contract_types
return cls._local_contracts["StreamManager"]
manifest_file = Path(__file__).parent / "manifest.json"
manifest_text = manifest_file.read_text()
manifest = PackageManifest.parse_raw(manifest_text)

if not manifest or not manifest.contract_types:
raise ValueError("Invalid manifest")

cls._local_contracts = manifest.contract_types
return cls._local_contracts.get("StreamManager")

@property
def contract(self) -> ContractInstance:
Expand Down Expand Up @@ -157,7 +166,7 @@ def set_validators(

def add_validators(
self,
*new_validators: Iterable[_ValidatorItem],
*new_validators: _ValidatorItem,
**txn_kwargs,
) -> ReceiptAPI:
return self.set_validators(
Expand All @@ -167,7 +176,7 @@ def add_validators(

def remove_validators(
self,
*validators: Iterable[_ValidatorItem],
*validators: _ValidatorItem,
**txn_kwargs,
) -> ReceiptAPI:
return self.set_validators(
Expand Down Expand Up @@ -307,14 +316,16 @@ class Stream(BaseInterfaceModel):
creation_receipt: Optional[ReceiptAPI] = None
transaction_hash: Optional[HexBytes] = None

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

return value

@validator("creator", pre=True)
@field_validator("creator", mode="before")
@classmethod
def validate_addresses(cls, value):
return (
value if isinstance(value, str) else cls.conversion_manager.convert(value, AddressType)
Expand Down
13 changes: 7 additions & 6 deletions sdk/py/apepay/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from enum import Enum

import click
from ape.types import AddressType
from apepay import Stream, StreamManager
from silverback import SilverbackApp
from eth_utils import to_checksum_address
from silverback import SilverbackApp, SilverbackStartupState

from .settings import Settings

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


SM = StreamManager(
address=os.environ.get("APEPAY_CONTRACT_ADDRESS")
or click.prompt("What address to use?", type=AddressType)
address=to_checksum_address(
os.environ.get("APEPAY_CONTRACT_ADDRESS") or click.prompt("What address to use?", type=str)
)
)

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


@app.on_startup()
async def app_started(state):
async def app_started(startup_state: SilverbackStartupState):
return await asyncio.gather(
# Start watching all active streams and claim any completed but unclaimed streams
*(
create_task_by_status(stream)
for stream in SM.all_streams()
for stream in SM.all_streams(startup_state.last_block_seen)
if stream.is_active or stream.amount_unlocked > 0
)
)
Expand Down
11 changes: 5 additions & 6 deletions sdk/py/apepay/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
from typing import Any

from apepay.utils import time_unit_to_timedelta
from pydantic import BaseSettings, validator
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APEPAY_", case_sensitive=True)

WARNING_LEVEL: timedelta = timedelta(days=2)
CRITICAL_LEVEL: timedelta = timedelta(hours=12)

@validator("WARNING_LEVEL", "CRITICAL_LEVEL", pre=True)
@field_validator("WARNING_LEVEL", "CRITICAL_LEVEL", mode="before")
def _normalize_timedelta(cls, value: Any) -> timedelta:
if isinstance(value, timedelta):
return value
Expand All @@ -27,7 +30,3 @@ def _normalize_timedelta(cls, value: Any) -> timedelta:
else:
multiplier, time_unit = value.split(" ")
return int(multiplier) * time_unit_to_timedelta(time_unit)

class Config:
env_prefix = "APEPAY_"
case_sensitive = True
2 changes: 1 addition & 1 deletion sdk/py/apepay/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def async_wrap_iter(it: Iterator) -> AsyncIterator:
"""Wrap blocking iterator into an asynchronous one"""
loop = asyncio.get_event_loop()
q = asyncio.Queue(1)
q: asyncio.Queue = asyncio.Queue(1)
exception = None
_END = object()

Expand Down

0 comments on commit 8519e4f

Please sign in to comment.