Skip to content

Commit

Permalink
docs: fix module path for SignbleMessage (ApeWorX#2175)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Jul 12, 2024
1 parent 5d2db65 commit a5cea52
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/ape/api/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def sign_message(self, msg: Any, **signer_options) -> Optional[MessageSignature]
:class:`~ape.types.signatures.SignableMessage`, str, int, and bytes.
See these
`docs <https://eth-account.readthedocs.io/en/stable/eth_account.html#eth_account.messages.SignableMessage>`__ # noqa: E501
for more type information on the ``SignableMessage`` type.
for more type information on the :class:`~ape.types.signatures.SignableMessage` type.
**signer_options: Additional kwargs given to the signer to modify the signing operation.
Returns:
Expand Down
12 changes: 8 additions & 4 deletions src/ape/types/signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from eth_utils import to_bytes, to_hex
from pydantic.dataclasses import dataclass

from ape.utils import log_instead_of_fail
from ape.utils import as_our_module, log_instead_of_fail

try:
# Only on Python 3.11
Expand All @@ -18,8 +18,11 @@
from ape.types import AddressType

# Fix 404 in doc link.
SignableMessage.__doc__ = (SignableMessage.__doc__ or "").replace(
"EIP-191_", "`EIP-191 <https://eips.ethereum.org/EIPS/eip-191>`__"
as_our_module(
SignableMessage,
doc_str=(SignableMessage.__doc__ or "").replace(
"EIP-191_", "`EIP-191 <https://eips.ethereum.org/EIPS/eip-191>`__"
),
)


Expand Down Expand Up @@ -124,7 +127,8 @@ def recover_signer(msg: SignableMessage, sig: MessageSignature) -> AddressType:
Get the address of the signer.
Args:
msg (``SignableMessage``): A formatted and signable message.
msg (:class:`~ape.types.signatures.SignableMessage`): A formatted and signable
message.
sig (:class:`~ape.types.signatures.MessageSignature`): Signature of the message.
Returns:
Expand Down
2 changes: 2 additions & 0 deletions src/ape/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ZERO_ADDRESS,
add_padding_to_strings,
allow_disconnected,
as_our_module,
cached_property,
extract_nested_value,
gas_estimation_error_message,
Expand Down Expand Up @@ -75,6 +76,7 @@
"abstractmethod",
"add_padding_to_strings",
"allow_disconnected",
"as_our_module",
"BaseInterface",
"BaseInterfaceModel",
"cached_property",
Expand Down
35 changes: 34 additions & 1 deletion src/ape/utils/misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import functools
import inspect
import json
import sys
from asyncio import gather
Expand All @@ -9,7 +10,7 @@
from importlib.metadata import PackageNotFoundError, distributions
from importlib.metadata import version as version_metadata
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, cast
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast

import requests
import yaml
Expand Down Expand Up @@ -543,6 +544,38 @@ def wrapped(*args, **kwargs):
return wrapper


_MOD_T = TypeVar("_MOD_T")


def as_our_module(cls_or_def: _MOD_T, doc_str: Optional[str] = None) -> _MOD_T:
"""
Ape sometimes reclaims definitions from other packages, such as
class:`~ape.types.signatures.SignableMessage`). When doing so, the doc str
may be different than ours, and the module may still refer to
the original package. This method steals the given class as-if
it were ours. Logic borrowed from starknet-py.
https://github.com/software-mansion/starknet.py/blob/0.10.1-alpha/starknet_py/utils/docs.py#L10-L24
Args:
cls_or_def (_MOD_T): The class or definition to borrow.
doc_str (str): Optionally change the doc string.
Returns:
The borrowed-version of the class or definition.
"""
if cls_or_def is None:
return cls_or_def

elif doc_str is not None:
cls_or_def.__doc__ = doc_str

frame = inspect.stack()[1]
if module := inspect.getmodule(frame[0]):
cls_or_def.__module__ = module.__name__

return cls_or_def


__all__ = [
"allow_disconnected",
"cached_property",
Expand Down
64 changes: 64 additions & 0 deletions tests/functional/test_signatures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from ape.types.signatures import MessageSignature, SignableMessage, TransactionSignature


@pytest.fixture
def signable_message():
version = b"E"
header = b"thereum Signed Message:\n32"
body = (
b"\x86\x05\x99\xc6\xfa\x0f\x05|po(\x1f\xe3\x84\xc0\x0f"
b"\x13\xb2\xa6\x91\xa3\xb8\x90\x01\xc0z\xa8\x17\xbe'\xf3\x13"
)
return SignableMessage(version=version, header=header, body=body)


@pytest.fixture
def signature(owner, signable_message):
return owner.sign_message(signable_message)


def test_signature_repr():
signature = TransactionSignature(v=0, r=b"123", s=b"456")
assert repr(signature) == "<TransactionSignature v=0 r=0x313233 s=0x343536>"


def test_signable_message_repr(signable_message):
actual = repr(signable_message)
expected_version = "E"
expected_header = "thereum Signed Message:\n32"
expected_body = "0x860599c6fa0f057c706f281fe384c00f13b2a691a3b89001c07aa817be27f313"
expected = (
f'SignableMessage(version="{expected_version}", header="{expected_header}", '
f'body="{expected_body}")'
)

assert actual == expected


def test_signature_from_rsv_and_vrs(signature):
rsv = signature.encode_rsv()
vrs = signature.encode_vrs()

# NOTE: Type declaring for sake of ensuring
# type-checking works since class-method is
# defined in base-class.
from_rsv: MessageSignature = signature.from_rsv(rsv)
from_vrs: MessageSignature = signature.from_vrs(vrs)
assert from_rsv == from_vrs == signature


def test_signable_message_module(signable_message):
"""
At the time of writing this, SignableMessage is a borrowed
construct from `eth_account.messages`. We are changing the module
manually so we are testing that it shows Ape's now.
"""
actual = signable_message.__module__
expected = "ape.types.signatures"
assert actual == expected

# Also show the 404-causing line in the __doc__ was fixed.
assert "EIP-191_" not in signable_message.__doc__
assert "EIP-191" in signable_message.__doc__
55 changes: 1 addition & 54 deletions tests/functional/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@
from hexbytes import HexBytes
from pydantic import BaseModel

from ape.types import (
AddressType,
ContractLog,
LogFilter,
MessageSignature,
SignableMessage,
TransactionSignature,
)
from ape.types import AddressType, ContractLog, LogFilter
from ape.utils import ZERO_ADDRESS

TXN_HASH = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa222222222222222222222222"
Expand Down Expand Up @@ -56,22 +49,6 @@ def log():
return ContractLog(**RAW_LOG)


@pytest.fixture
def signable_message():
version = b"E"
header = b"thereum Signed Message:\n32"
body = (
b"\x86\x05\x99\xc6\xfa\x0f\x05|po(\x1f\xe3\x84\xc0\x0f"
b"\x13\xb2\xa6\x91\xa3\xb8\x90\x01\xc0z\xa8\x17\xbe'\xf3\x13"
)
return SignableMessage(version=version, header=header, body=body)


@pytest.fixture
def signature(owner, signable_message):
return owner.sign_message(signable_message)


def test_contract_log_serialization(log, zero_address):
obj = ContractLog.model_validate(log.model_dump())
assert obj.contract_address == zero_address
Expand Down Expand Up @@ -130,36 +107,6 @@ def test_topic_filter_encoding():
]


def test_signature_repr():
signature = TransactionSignature(v=0, r=b"123", s=b"456")
assert repr(signature) == "<TransactionSignature v=0 r=0x313233 s=0x343536>"


def test_signable_message_repr(signable_message):
actual = repr(signable_message)
expected_version = "E"
expected_header = "thereum Signed Message:\n32"
expected_body = "0x860599c6fa0f057c706f281fe384c00f13b2a691a3b89001c07aa817be27f313"
expected = (
f'SignableMessage(version="{expected_version}", header="{expected_header}", '
f'body="{expected_body}")'
)

assert actual == expected


def test_signature_from_rsv_and_vrs(signature):
rsv = signature.encode_rsv()
vrs = signature.encode_vrs()

# NOTE: Type declaring for sake of ensuring
# type-checking works since class-method is
# defined in base-class.
from_rsv: MessageSignature = signature.from_rsv(rsv)
from_vrs: MessageSignature = signature.from_vrs(vrs)
assert from_rsv == from_vrs == signature


def test_address_type(owner):
class MyModel(BaseModel):
addr: AddressType
Expand Down

0 comments on commit a5cea52

Please sign in to comment.