Skip to content

Commit 991803e

Browse files
authored
Add deepcopy to [Nonempty]OrderedSet (#440)
* Add deepcopy to [Nonempty]OrderedSet * Disable parallelization in integration test
1 parent 22ebd67 commit 991803e

File tree

5 files changed

+188
-11
lines changed

5 files changed

+188
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
58a20100003232232323232323374a90001bb1498c8d4008400401448c8c92610013330063758a00246eb400452f580264c66ae712410c4e616d654572726f723a207a004984c98cd5ce2481144e616d654572726f723a2076616c696461746f72004984c98cd5ce24810d4e616d654572726f723a20723300498888cc8c014894ccd55cf8008a802099aba0300335742002660040046ae8800400800c8c8c0040040041

integration-test/run_tests.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ while true; do
9393
sleep 2
9494
done
9595

96-
poetry run pytest -m "not (CardanoCLI)" -s -vv -n 4 "$ROOT"/test --cov=pycardano --cov-config=../.coveragerc --cov-report=xml:../coverage.xml
96+
poetry run pytest -m "not (CardanoCLI)" -s -vv "$ROOT"/test --cov=pycardano --cov-config=../.coveragerc --cov-report=xml:../coverage.xml
9797

9898
# Cleanup
9999
docker compose -f docker-compose-chang.yml down --volumes --remove-orphans

integration-test/test/test_plutus.py

+77
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Dict, Union
55

66
import cbor2
7+
import ogmios as python_ogmios
78
import pytest
89
from retry import retry
910

@@ -444,6 +445,82 @@ def test_plutus_v3(self):
444445

445446
self.assert_output(taker_address, take_output)
446447

448+
def test_plutus_v3_unroll(self):
449+
# ----------- Giver give ---------------
450+
451+
with open(
452+
"./plutus_scripts/unroll.plutus",
453+
"r",
454+
) as f:
455+
script_hex = f.read()
456+
hello_world_script = bytes.fromhex(script_hex)
457+
458+
script_hash = plutus_script_hash(PlutusV3Script(hello_world_script))
459+
460+
script_address = Address(script_hash, network=self.NETWORK)
461+
462+
giver_address = Address(self.payment_vkey.hash(), network=self.NETWORK)
463+
464+
builder = TransactionBuilder(self.chain_context)
465+
builder.add_input_address(giver_address)
466+
builder.add_output(TransactionOutput(script_address, 50000000, datum=Unit()))
467+
468+
signed_tx = builder.build_and_sign([self.payment_skey], giver_address)
469+
470+
print("############### Transaction created ###############")
471+
print(signed_tx)
472+
print(signed_tx.to_cbor_hex())
473+
print("############### Submitting transaction ###############")
474+
self.chain_context.submit_tx(signed_tx)
475+
time.sleep(3)
476+
477+
# ----------- Taker take ---------------
478+
479+
utxo_to_spend = self.chain_context.utxos(script_address)[0]
480+
481+
taker_address = Address(self.payment_vkey.hash(), network=self.NETWORK)
482+
483+
builder = TransactionBuilder(self.chain_context)
484+
builder.ttl = self.chain_context.last_block_slot + 10
485+
486+
reward_account = Address(
487+
staking_part=self.stake_key_pair.verification_key.hash(),
488+
network=self.NETWORK,
489+
)
490+
491+
builder.add_script_input(
492+
utxo_to_spend, PlutusV3Script(hello_world_script), redeemer=Redeemer(0)
493+
)
494+
builder.add_proposal(
495+
deposit=1234122,
496+
reward_account=bytes(reward_account),
497+
gov_action=ParameterChangeAction(
498+
gov_action_id=GovActionId(
499+
gov_action_index=0,
500+
transaction_id=utxo_to_spend.input.transaction_id,
501+
),
502+
protocol_param_update=ProtocolParamUpdate(
503+
min_fee_b=1000,
504+
),
505+
policy_hash=None,
506+
),
507+
anchor=Anchor(
508+
url="https://test-drep.com",
509+
data_hash=AnchorDataHash(bytes.fromhex("0" * 64)),
510+
),
511+
)
512+
513+
with pytest.raises(python_ogmios.errors.ResponseError) as e:
514+
builder.build_and_sign([self.payment_skey], taker_address)
515+
516+
for v in [
517+
"1234122",
518+
"1000",
519+
reward_account.staking_part.payload.hex(),
520+
utxo_to_spend.input.transaction_id.payload.hex(),
521+
]:
522+
assert v in str(e.value)
523+
447524

448525
class TestPlutusKupoOgmios(TestPlutus):
449526
@classmethod

pycardano/serialization.py

+6-10
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,7 @@
4141
logger.warning("Failed to remove semantic decoder for CBOR tag 258", e)
4242
pass
4343

44-
from cbor2 import (
45-
CBOREncoder,
46-
CBORSimpleValue,
47-
CBORTag,
48-
FrozenDict,
49-
dumps,
50-
undefined,
51-
)
44+
from cbor2 import CBOREncoder, CBORSimpleValue, CBORTag, FrozenDict, dumps, undefined
5245
from frozenlist import FrozenList
5346
from pprintpp import pformat
5447

@@ -948,8 +941,8 @@ def __repr__(self):
948941
def __copy__(self):
949942
return self.__class__(self)
950943

951-
def __deepcopy__(self, memodict={}):
952-
return self.__class__(deepcopy(self.data))
944+
def __deepcopy__(self, memo):
945+
return self.__class__(deepcopy(self.data, memo))
953946

954947
def validate(self):
955948
for key, value in self.data.items():
@@ -1082,6 +1075,9 @@ def from_primitive(
10821075

10831076
raise ValueError(f"Cannot deserialize {value} to {cls}")
10841077

1078+
def __deepcopy__(self, memo):
1079+
return self.__class__(deepcopy(list(self), memo), use_tag=self._use_tag)
1080+
10851081

10861082
class NonEmptyOrderedSet(OrderedSet[T]):
10871083
def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True):

test/pycardano/test_serialization.py

+103
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict, deque
2+
from copy import deepcopy
23
from dataclasses import dataclass, field
34
from test.pycardano.util import check_two_way_cbor
45
from typing import (
@@ -879,3 +880,105 @@ class ChildCodedClass(TestCodedClass):
879880
assert restored.value == "test"
880881
assert restored.numbers == [1, 2]
881882
assert restored.extra == "extra"
883+
884+
885+
def test_ordered_set_deepcopy():
886+
"""Test the deepcopy implementation of OrderedSet."""
887+
# Test basic deepcopy
888+
889+
class MyOrderedSet(OrderedSet):
890+
pass
891+
892+
s = MyOrderedSet([1, 2, 3], use_tag=True)
893+
s_copy = deepcopy(s)
894+
895+
assert s == s_copy
896+
assert s is not s_copy
897+
assert s._use_tag == s_copy._use_tag
898+
899+
# Test that modifications don't affect each other
900+
s_copy.append(4)
901+
assert 4 in s_copy
902+
assert 4 not in s
903+
904+
# Test with complex objects
905+
class TestObj:
906+
def __init__(self, value):
907+
self.value = value
908+
909+
def __str__(self):
910+
return f"TestObj({self.value})"
911+
912+
obj1 = TestObj("a")
913+
obj2 = TestObj("b")
914+
915+
s = MyOrderedSet([obj1, obj2], use_tag=False)
916+
s_copy = deepcopy(s)
917+
918+
# Objects should be equal but not the same instances
919+
assert len(s) == len(s_copy) == 2
920+
assert s[0].value == s_copy[0].value
921+
assert s[1].value == s_copy[1].value
922+
assert s[0] is not s_copy[0]
923+
assert s[1] is not s_copy[1]
924+
925+
# Test that memodict works with shared references
926+
shared_obj = TestObj("shared")
927+
s = MyOrderedSet([shared_obj, shared_obj], use_tag=True) # Same object twice
928+
929+
s_copy = deepcopy(s)
930+
# In the copy, both elements should be the same object (preserved reference)
931+
assert len(s_copy) == 1
932+
assert s_copy[0] is not shared_obj
933+
934+
935+
def test_non_empty_ordered_set_deepcopy():
936+
"""Test the deepcopy implementation of NonEmptyOrderedSet."""
937+
938+
class MyNonEmptyOrderedSet(NonEmptyOrderedSet):
939+
pass
940+
941+
# Test basic deepcopy
942+
s = MyNonEmptyOrderedSet([1, 2, 3], use_tag=True)
943+
s_copy = deepcopy(s)
944+
945+
assert s == s_copy
946+
assert s is not s_copy
947+
assert s._use_tag == s_copy._use_tag
948+
949+
# Test with nested lists
950+
nested_list = [[1, 2], [3, 4]]
951+
s = MyNonEmptyOrderedSet(nested_list, use_tag=False)
952+
s_copy = deepcopy(s)
953+
954+
# Lists should be equal but not the same instances
955+
assert s[0] == s_copy[0]
956+
assert s[1] == s_copy[1]
957+
assert s[0] is not s_copy[0]
958+
assert s[1] is not s_copy[1]
959+
960+
# Modifying the copy shouldn't affect the original
961+
s_copy[0].append(5)
962+
assert s[0] == [1, 2]
963+
assert s_copy[0] == [1, 2, 5]
964+
965+
# Test complex nesting with CBORSerializable objects
966+
@dataclass
967+
class TestData(MapCBORSerializable):
968+
value: int = 0
969+
970+
obj1 = TestData(1)
971+
obj2 = TestData(2)
972+
s = MyNonEmptyOrderedSet([obj1, obj2], use_tag=True)
973+
s_copy = deepcopy(s)
974+
975+
# Objects should be equal but not the same instances
976+
assert s[0].value == s_copy[0].value
977+
assert s[1].value == s_copy[1].value
978+
assert s[0] is not s_copy[0]
979+
assert s[1] is not s_copy[1]
980+
981+
# Modifying the copy shouldn't affect the original
982+
s_copy[0].value = 100
983+
assert s[0].value == 1
984+
assert s_copy[0].value == 100

0 commit comments

Comments
 (0)