Skip to content

Commit

Permalink
feat: add _replace implementation for named tuples, make `algopy.Co…
Browse files Browse the repository at this point in the history
…mpiledContract` and `algopy.CompiledLogicSig` named tuples
  • Loading branch information
daniel-makerx authored and achidlow committed Oct 25, 2024
1 parent 5138d00 commit 93a47f2
Show file tree
Hide file tree
Showing 23 changed files with 3,191 additions and 1,290 deletions.
4 changes: 2 additions & 2 deletions examples/sizes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
chained_assignment/ChainedAssignment 87 87 - | 30 29 -
compile/Hello 217 181 - | 107 85 -
compile/HelloBase 198 156 - | 98 75 -
compile/HelloFactory 7222 6676 - | 1003 750 -
compile/HelloFactory 7401 6824 - | 1115 842 -
compile/HelloOtherConstants 306 281 - | 137 121 -
compile/HelloPrfx 211 168 - | 102 77 -
compile/HelloTmpl 211 168 - | 102 77 -
Expand Down Expand Up @@ -130,4 +130,4 @@
unssa/UnSSA 432 368 - | 241 204 -
voting/VotingRoundApp 1593 1483 - | 734 649 -
with_reentrancy/WithReentrancy 255 242 - | 132 122 -
Total 69012 53428 53369 | 32727 21672 21628
Total 69191 53576 53517 | 32839 21764 21720
51 changes: 49 additions & 2 deletions src/puyapy/awst_build/eb/tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@

from puyapy.awst_build import pytypes
from puyapy.awst_build.eb import _expect as expect
from puyapy.awst_build.eb._base import GenericTypeBuilder, InstanceExpressionBuilder
from puyapy.awst_build.eb._base import (
FunctionBuilder,
GenericTypeBuilder,
InstanceExpressionBuilder,
)
from puyapy.awst_build.eb._literals import LiteralBuilderImpl
from puyapy.awst_build.eb._utils import constant_bool_and_error, dummy_value
from puyapy.awst_build.eb.bool import BoolExpressionBuilder
Expand Down Expand Up @@ -287,7 +291,7 @@ def to_bytes(self, location: SourceLocation) -> Expression:
raise CodeError(f"cannot serialize {self.pytype}", location)

@typing.override
def member_access(self, name: str, location: SourceLocation) -> InstanceBuilder:
def member_access(self, name: str, location: SourceLocation) -> NodeBuilder:
if isinstance(self.pytype, pytypes.NamedTupleType):
item_typ = self.pytype.fields.get(name)
if item_typ is not None:
Expand All @@ -297,6 +301,8 @@ def member_access(self, name: str, location: SourceLocation) -> InstanceBuilder:
source_location=location,
)
return builder_for_instance(item_typ, item_expr)
elif name == "_replace":
return _Replace(self, self.pytype, location)
if name in dir(tuple()): # noqa: C408
raise CodeError("method is not currently supported", location)
raise CodeError("unrecognised member access", location)
Expand Down Expand Up @@ -462,6 +468,47 @@ def compare(
return _compare(self, other, op, location)


class _Replace(FunctionBuilder):
def __init__(
self,
instance: TupleExpressionBuilder,
named_tuple_type: pytypes.NamedTupleType,
location: SourceLocation,
):
super().__init__(location)
self.instance = instance
self.named_tuple_type = named_tuple_type

@typing.override
def call(
self,
args: Sequence[NodeBuilder],
arg_kinds: list[mypy.nodes.ArgKind],
arg_names: list[str | None],
location: SourceLocation,
) -> InstanceBuilder:
pytype = self.named_tuple_type
field_mapping, _ = get_arg_mapping(
optional_kw_only=list(pytype.fields),
args=args,
arg_names=arg_names,
call_location=location,
raise_on_missing=False,
)
base_expr = self.instance.single_eval().resolve()
items = list[Expression]()
for idx, (field_name, field_pytype) in enumerate(pytype.fields.items()):
new_value = field_mapping.get(field_name)
if new_value is not None:
item_builder = expect.argument_of_type_else_dummy(new_value, field_pytype)
item = item_builder.resolve()
else:
item = TupleItemExpression(base=base_expr, index=idx, source_location=location)
items.append(item)
new_tuple = TupleExpression(items=items, wtype=pytype.wtype, source_location=location)
return TupleExpressionBuilder(new_tuple, pytype)


def _compare(
lhs: InstanceBuilder[pytypes.TupleType],
rhs: InstanceBuilder,
Expand Down
2 changes: 1 addition & 1 deletion src/puyapy/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

# this should contain the lowest version number that this compiler does NOT support
# i.e. the next minor version after what is defined in stubs/pyproject.toml:tool.poetry.version
MAX_SUPPORTED_ALGOPY_VERSION_EX = version.parse("2.2.0")
MAX_SUPPORTED_ALGOPY_VERSION_EX = version.parse("2.3.0")
MIN_SUPPORTED_ALGOPY_VERSION = version.parse(f"{MAX_SUPPORTED_ALGOPY_VERSION_EX.major}.0.0")

logger = log.get_logger(__name__)
Expand Down
92 changes: 42 additions & 50 deletions stubs/algopy-stubs/_compiled.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,73 +8,65 @@ from algopy import (
UInt64,
)

class CompiledContract(typing.Protocol):
class CompiledContract(typing.NamedTuple):
"""
Provides compiled programs and state allocation values for a Contract.
Create by calling [`compile_contract`](#algopy.compile_contract).
"""

@property
def approval_program(self) -> tuple[Bytes, Bytes]:
"""
Approval program pages for a contract, after template variables have been replaced
and compiled to AVM bytecode
"""
approval_program: tuple[Bytes, Bytes]
"""
Approval program pages for a contract, after template variables have been replaced
and compiled to AVM bytecode
"""

@property
def clear_state_program(self) -> tuple[Bytes, Bytes]:
"""
Clear state program pages for a contract, after template variables have been replaced
and compiled to AVM bytecode
"""
clear_state_program: tuple[Bytes, Bytes]
"""
Clear state program pages for a contract, after template variables have been replaced
and compiled to AVM bytecode
"""

@property
def extra_program_pages(self) -> UInt64:
"""
By default, provides extra program pages required based on approval and clear state program
size, can be overridden when calling compile_contract
"""
extra_program_pages: UInt64
"""
By default, provides extra program pages required based on approval and clear state program
size, can be overridden when calling compile_contract
"""

@property
def global_uints(self) -> UInt64:
"""
By default, provides global num uints based on contract state totals, can be overridden
when calling compile_contract
"""
global_uints: UInt64
"""
By default, provides global num uints based on contract state totals, can be overridden
when calling compile_contract
"""

@property
def global_bytes(self) -> UInt64:
"""
By default, provides global num bytes based on contract state totals, can be overridden
when calling compile_contract
"""
global_bytes: UInt64
"""
By default, provides global num bytes based on contract state totals, can be overridden
when calling compile_contract
"""

@property
def local_uints(self) -> UInt64:
"""
By default, provides local num uints based on contract state totals, can be overridden
when calling compile_contract
"""
local_uints: UInt64
"""
By default, provides local num uints based on contract state totals, can be overridden
when calling compile_contract
"""

@property
def local_bytes(self) -> UInt64:
"""
By default, provides local num bytes based on contract state totals, can be overridden
when calling compile_contract
"""
local_bytes: UInt64
"""
By default, provides local num bytes based on contract state totals, can be overridden
when calling compile_contract
"""

class CompiledLogicSig(typing.Protocol):
class CompiledLogicSig(typing.NamedTuple):
"""
Provides account for a Logic Signature.
Create by calling [`compile_logicsig`](#algopy.compile_logicsig).
"""

@property
def account(self) -> Account:
"""
Address of a logic sig program, after template variables have been replaced and compiled
to AVM bytecode
"""
account: Account
"""
Address of a logic sig program, after template variables have been replaced and compiled
to AVM bytecode
"""

def compile_contract(
contract: type[Contract],
Expand Down
2 changes: 1 addition & 1 deletion stubs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "algorand-python"
# this version represents the version of the stub API's and should follow semver semantics
# when updating this value also update src/compile.py:MAX_SUPPORTED_ALGOPY_VERSION_EX if it is a major/minor change
# also see stubs/README.md#versioning
version = "2.1.1"
version = "2.2.0"
description = "API for writing Algorand Python Smart contracts"
authors = ["Algorand Foundation <contact@algorand.foundation>"]
readme = "README.md"
Expand Down
28 changes: 28 additions & 0 deletions test_cases/compile/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Global,
OnCompleteAction,
String,
UInt64,
arc4,
compile_contract,
compile_logicsig,
Expand Down Expand Up @@ -233,6 +234,33 @@ def test_arc4_create_large(self) -> None:
app_id=app,
)

@arc4.abimethod()
def test_arc4_create_modified_compiled(self) -> None:
compiled = compile_contract(Hello)
compiled = compiled._replace(
local_uints=UInt64(3),
global_uints=UInt64(4),
local_bytes=UInt64(5),
global_bytes=UInt64(6),
)
app = arc4.arc4_create(
Hello.create,
String("hey"),
compiled=compiled,
).created_app

assert app.local_num_uint == 3
assert app.global_num_uint == 4
assert app.local_num_bytes == 5
assert app.global_num_bytes == 6

result, _txn = arc4.abi_call(Hello.greet, "there", app_id=app)

assert result == "hey there"

# delete the app
arc4.abi_call(Hello.delete, app_id=app)

@arc4.abimethod()
def test_arc4_update(self) -> None:
# create app
Expand Down
Loading

0 comments on commit 93a47f2

Please sign in to comment.