Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create pytest plugin #5

Merged
merged 4 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = I001,I005,Q000,S101,WPS110,WPS111,WPS114,WPS115,WPS120,WPS201,WPS202,WPS210,WPS211,WPS214,WPS221,WPS226,WPS230,WPS232,WPS303,WPS306,WPS305,WPS326,WPS410,WPS428,WPS430,WPS432,WPS442,W503,DAR201
ignore = I001,I005,Q000,S101,WPS110,WPS111,WPS114,WPS115,WPS120,WPS201,WPS202,WPS210,WPS211,WPS213,WPS214,WPS221,WPS226,WPS230,WPS232,WPS303,WPS306,WPS305,WPS326,WPS410,WPS428,WPS430,WPS432,WPS442,W503,DAR201
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
max-line-length = 88
per-file-ignores =
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ mkdocs-material = "^7.3.3"
pytest-xprocess = "^0.18.1"
bump2version = "^1.0.1"

[tool.poetry.plugins.pytest11]
pytest_anchorpy = "anchorpy.pytest_plugin"


[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
1 change: 1 addition & 0 deletions src/anchorpy/coder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""This subpackage defines the Coder class."""
1 change: 1 addition & 0 deletions src/anchorpy/coder/accounts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""This module provides `AccountsCoder` and `account_discriminator`."""
from hashlib import sha256
from typing import Tuple, Any
from construct import Adapter, Sequence, Bytes, Switch, Container
Expand Down
1 change: 1 addition & 0 deletions src/anchorpy/coder/coder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Provides the Coder class."""
from anchorpy.coder.accounts import AccountsCoder
from anchorpy.coder.event import EventCoder
from anchorpy.coder.instruction import InstructionCoder
Expand Down
10 changes: 10 additions & 0 deletions src/anchorpy/coder/common.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Common utilities for encoding and decoding."""
from typing import Dict, Union
from hashlib import sha256
from inflection import underscore
Expand Down Expand Up @@ -89,6 +90,15 @@ def _variant_size(idl: Idl, variant: IdlEnumVariant) -> int:


def account_size(idl: Idl, idl_account: IdlTypeDef) -> int:
"""Calculate account size in bytes.

Args:
idl: The parsed `Idl` instance.
idl_account: An item from `idl.accounts`.

Returns:
Account size.
"""
idl_account_type = idl_account.type
if isinstance(idl_account_type, IdlTypeDefTyEnum):
variant_sizes = (
Expand Down
9 changes: 9 additions & 0 deletions src/anchorpy/coder/idl.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ def typedef_layout(


def field_layout(field: IdlField, types: List[IdlTypeDef]) -> Construct:
"""Map IDL spec to `borsh-construct` types.

Args:
field: field object from the IDL.
types: IDL type definitions.

Returns:
`Construct` object from `borsh-construct`.
"""
field_name = field.name if field.name else ""
if isinstance(field.type, str):
return field_name / FIELD_TYPE_MAP[field.type]
Expand Down
1 change: 1 addition & 0 deletions src/anchorpy/program/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""This subpacakge defines the `Program` class."""
30 changes: 27 additions & 3 deletions src/anchorpy/program/common.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Common utilities."""
from dataclasses import dataclass
from typing import Dict, List, Any, Union, cast, get_args, Tuple
from construct import Container
Expand All @@ -9,14 +10,20 @@
IdlInstruction,
IdlAccountItem,
)
from anchorpy.program.context import Accounts

AddressType = Union[PublicKey, str]


def parse_idl_errors(idl: Idl) -> Dict[int, str]:
"""Turns IDL errors into something readable.
"""Turn IDL errors into something readable.

Uses message if available, otherwise name."""
Uses message if available, otherwise name.

Args:
idl: Parsed `Idl` instance.

"""
errors = {}
for e in idl.errors:
msg = e.msg if e.msg else e.name
Expand All @@ -39,7 +46,16 @@ def to_instruction(idl_ix: IdlInstruction, args: Tuple) -> Instruction:
return Instruction(data=ix, name=idl_ix.name)


def validate_accounts(ix_accounts: List[IdlAccountItem], accounts):
def validate_accounts(ix_accounts: List[IdlAccountItem], accounts: Accounts):
"""Check that accounts passed in `ctx` match the IDL.

Args:
ix_accounts: Accounts from the IDL.
accounts: Accounts from the `ctx` arg.

Raises:
ValueError: If `ctx` accounts don't match the IDL.
"""
for acc in ix_accounts:
if isinstance(acc, get_args(IdlAccounts)):
idl_accounts = cast(IdlAccounts, acc)
Expand All @@ -49,6 +65,14 @@ def validate_accounts(ix_accounts: List[IdlAccountItem], accounts):


def translate_address(address: AddressType):
"""Convert `str | PublicKey` into `PublicKey`.

Args:
address: Public key as string or `PublicKey`.

Returns:
Public key as `PublicKey`.
"""
if isinstance(address, str):
return PublicKey(address)
return address
24 changes: 24 additions & 0 deletions src/anchorpy/program/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ def parse_logs(self, logs: List[str], callback: Callable[[Event], None]) -> None
def handle_log(
self, execution: ExecutionContext, log: str
) -> Tuple[Optional[Event], Optional[str], bool]:
"""Main log handler.

Args:
execution: The execution stack.
log: log string from the RPC node.

Returns:
A three element array of the event, the next program
that was invoked for CPI, and a boolean indicating if
a program has completed execution (and thus should be popped off the
execution stack).
"""
# Executing program is this program.
if execution.stack and execution.program() == str(self.program_id):
return self.handle_program_log(log)
Expand All @@ -63,6 +75,12 @@ def handle_log(
def handle_program_log(
self, log: str
) -> Tuple[Optional[Event], Optional[str], bool]:
"""Handle logs from *this* program.

Args:
log: log string from the RPC node.

"""
# This is a `msg!` log.
if log.startswith("Program log:"):
log_str = log[LOG_START_INDEX:]
Expand All @@ -75,6 +93,12 @@ def handle_program_log(
return (None, *self.handle_system_log(log))

def handle_system_log(self, log: str) -> Tuple[Optional[str], bool]:
"""Handle logs when the current program being executing is *not* this.

Args:
log: log string from the RPC node.

"""
log_start = log.split(":")[0]
if log_start.split("Program ")[1].split(" ")[1] == "success":
return None, True
Expand Down
1 change: 1 addition & 0 deletions src/anchorpy/program/namespace/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""This subpackage deals with the dynamic namespaces under `Program`."""
6 changes: 6 additions & 0 deletions src/anchorpy/program/namespace/account.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Provides the `AccountClient` class."""
import base64
from dataclasses import dataclass
from base58 import b58encode
Expand All @@ -23,6 +24,11 @@ def build_account(
program_id: PublicKey,
provider: Provider,
) -> Dict[str, "AccountClient"]:
"""Generate the `.account` namespace.

Returns:
[type]: [description]
"""
accounts_fns = {}
for idl_account in idl.accounts:
account_client = AccountClient(idl, idl_account, coder, program_id, provider)
Expand Down
19 changes: 17 additions & 2 deletions src/anchorpy/program/namespace/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,30 @@ def __call__(
)

def accounts(self, accs: Accounts) -> List[AccountMeta]:
"""Order the accounts for this instruction."""
"""Order the accounts for this instruction.

Args:
accs: Accounts from `ctx` kwarg.

Returns:
Ordered and flattened accounts.
"""
return accounts_array(accs, self.idl_ix.accounts)


def accounts_array(
ctx: Accounts,
accounts: Sequence[IdlAccountItem],
) -> List[AccountMeta]:
"""Create a list of AccountMeta from a (possibly nested) dict of accounts."""
"""Create a list of AccountMeta from a (possibly nested) dict of accounts.

Args:
ctx: `accounts` field from the `Context` object.
accounts: accounts from the IDL.

Returns:
AccountMeta objects.
"""
accounts_ret: List[AccountMeta] = []
for acc in accounts:
if isinstance(acc, IdlAccounts):
Expand Down
2 changes: 2 additions & 0 deletions src/anchorpy/program/namespace/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def build_rpc_item( # ts: RpcFactory
idl_errors: Dict[int, str],
provider: Provider,
) -> RpcFn:
"""Build the function that sends transactions for the given method."""

async def rpc_fn(*args: Any, ctx: Context = EMPTY_CONTEXT) -> TransactionSignature:
tx = tx_fn(*args, ctx=ctx)
check_args_length(idl_ix, args)
Expand Down
5 changes: 5 additions & 0 deletions src/anchorpy/program/namespace/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ def build_simulate_item(
program_id: PublicKey,
idl: Idl,
) -> SimulateFn:
"""Build the function to simulate transactions for a given method of a program.

Returns a list of deserialized events and raw program logs.
"""

async def simulate_fn(*args: Any, ctx: Context = EMPTY_CONTEXT) -> SimulateResponse:
tx = tx_fn(*args, ctx=ctx)
check_args_length(idl_ix, args)
Expand Down
Loading