From ccc75d2feedd9f1d469a244b48c35d33ac0a6ae1 Mon Sep 17 00:00:00 2001
From: vtexier <vit@free.fr>
Date: Sat, 3 Nov 2018 11:34:00 +0100
Subject: [PATCH] issue #52 fix type hint problem in outputSource.conditions
 (BC broken)

Output instances in transactions get condition argument as text only now, and check grammar before storing condition as Condition instance.
---
 duniterpy/documents/transaction.py  | 55 ++++++++++++++++-------------
 examples/send_transaction.py        | 10 ++----
 tests/documents/test_transaction.py | 32 ++++++++---------
 3 files changed, 49 insertions(+), 48 deletions(-)

diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py
index aab2eded..871cc5ac 100644
--- a/duniterpy/documents/transaction.py
+++ b/duniterpy/documents/transaction.py
@@ -141,17 +141,17 @@ class OutputSource:
     """
     re_inline = re.compile("([0-9]+):([0-9]+):(.*)\n")
 
-    def __init__(self, amount: int, base: int, conditions: Union[str, Condition]) -> None:
+    def __init__(self, amount: int, base: int, condition: str) -> None:
         """
         Init OutputSource instance
 
         :param amount: Amount of the output
         :param base: Base number
-        :param conditions: Conditions expression
+        :param condition: Condition expression
         """
         self.amount = amount
         self.base = base
-        self.conditions = conditions
+        self.condition = self.condition_from_text(condition)
 
     @classmethod
     def from_inline(cls: Type[OutputSourceType], inline: str) -> OutputSourceType:
@@ -166,14 +166,9 @@ class OutputSource:
             raise MalformedDocumentError("Inline output")
         amount = int(data.group(1))
         base = int(data.group(2))
-        conditions_text = data.group(3)
-        try:
-            conditions = pypeg2.parse(conditions_text, output.Condition)
-        except SyntaxError:
-            # Invalid conditions are possible, see https://github.com/duniter/duniter/issues/1156
-            # In such a case, they are store "as-is" and considered unlockable
-            conditions = conditions_text
-        return cls(amount, base, conditions)
+        condition_text = data.group(3)
+
+        return cls(amount, base, condition_text)
 
     def inline(self) -> str:
         """
@@ -181,11 +176,24 @@ class OutputSource:
 
         :return:
         """
-        if type(self.conditions) is str:
-            return "{0}:{1}:{2}".format(self.amount, self.base, self.conditions)
-        else:
-            return "{0}:{1}:{2}".format(self.amount, self.base,
-                                        pypeg2.compose(self.conditions, output.Condition))
+        return "{0}:{1}:{2}".format(self.amount, self.base,
+                                    pypeg2.compose(self.condition, output.Condition))
+
+    @staticmethod
+    def condition_from_text(text) -> Condition:
+        """
+        Return a Condition instance with PEG grammar from text
+
+        :param text: PEG parsable string
+        :return:
+        """
+        try:
+            condition = pypeg2.parse(text, output.Condition)
+        except SyntaxError:
+            # Invalid conditions are possible, see https://github.com/duniter/duniter/issues/1156
+            # In such a case, they are store as empty PEG grammar object and considered unlockable
+            condition = Condition(text)
+        return condition
 
 
 # required to type hint cls in classmethod
@@ -759,7 +767,7 @@ class SimpleTransaction(Transaction):
         simple "SIG" functions, and the outputs must be simple
         SIG conditions.
 
-        :param duniterpy.documents.Transaction tx: the transaction to check
+        :param tx: the transaction to check
 
         :return: True if a simple transaction
         """
@@ -772,12 +780,11 @@ class SimpleTransaction(Transaction):
             elif type(unlock.parameters[0]) is not SIGParameter:
                 simple = False
         for o in tx.outputs:
-            # If condition is str type, it is unlockable
-            if type(o.conditions) is str:
+            # if right condition is not None...
+            if getattr('right', o.condition, None):
                 simple = False
-            else:
-                if getattr('right', o.conditions, None):
-                    simple = False
-                elif type(o.conditions.left) is not output.SIG:
-                    simple = False
+                # if left is not SIG...
+            elif type(o.condition.left) is not output.SIG:
+                simple = False
+
         return simple
diff --git a/examples/send_transaction.py b/examples/send_transaction.py
index e169d782..6ce68a65 100644
--- a/examples/send_transaction.py
+++ b/examples/send_transaction.py
@@ -5,7 +5,6 @@ from duniterpy.api import bma
 from duniterpy.api.client import Client
 from duniterpy.documents import BlockUID, Transaction
 from duniterpy.documents.transaction import InputSource, OutputSource, Unlock, SIGParameter
-from duniterpy.grammars.output import Condition, SIG
 from duniterpy.key import SigningKey
 
 # CONFIG #######################################
@@ -61,12 +60,7 @@ def get_transaction_document(current_block: dict, source: dict, from_pubkey: str
 
     # lists of outputs
     outputs = [
-        OutputSource(
-            amount=source['amount'],
-            base=source['base'],
-            # only the receiver of the output can use it as input in another transaction
-            conditions=Condition.token(SIG.token(to_pubkey))
-        )
+        OutputSource(amount=source['amount'], base=source['base'], condition="SIG({0})".format(to_pubkey))
     ]
 
     transaction = Transaction(
@@ -79,7 +73,7 @@ def get_transaction_document(current_block: dict, source: dict, from_pubkey: str
         unlocks=unlocks,
         outputs=outputs,
         comment='',
-        signatures=None
+        signatures=[]
     )
 
     return transaction
diff --git a/tests/documents/test_transaction.py b/tests/documents/test_transaction.py
index a14fe4e1..e27f659a 100644
--- a/tests/documents/test_transaction.py
+++ b/tests/documents/test_transaction.py
@@ -191,13 +191,13 @@ class Test_Transaction(unittest.TestCase):
 
         self.assertEqual(tx.outputs[0].amount, 120)
         self.assertEqual(tx.outputs[0].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
+        self.assertEqual(pypeg2.compose(tx.outputs[0].condition, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
         self.assertEqual(tx.outputs[1].amount, 146)
         self.assertEqual(tx.outputs[1].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
+        self.assertEqual(pypeg2.compose(tx.outputs[1].condition, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
         self.assertEqual(tx.outputs[2].amount, 49)
         self.assertEqual(tx.outputs[2].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
+        self.assertEqual(pypeg2.compose(tx.outputs[2].condition, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
 
         self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)")
 
@@ -225,12 +225,12 @@ class Test_Transaction(unittest.TestCase):
 
         self.assertEqual(tx.outputs[0].amount, 90)
         self.assertEqual(tx.outputs[0].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(5zDvFjJB1PGDQNiExpfzL9c1tQGs6xPA8mf1phr3VoVi)")
-        self.assertEqual(type(tx.outputs[0].conditions.left), output.SIG)
+        self.assertEqual(pypeg2.compose(tx.outputs[0].condition, output.Condition), "SIG(5zDvFjJB1PGDQNiExpfzL9c1tQGs6xPA8mf1phr3VoVi)")
+        self.assertEqual(type(tx.outputs[0].condition.left), output.SIG)
         self.assertEqual(tx.outputs[1].amount, 10)
         self.assertEqual(tx.outputs[1].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(GNPdPNwSJAYw7ixkDeibo3YpdELgLmrZ2Q86HF4cyg92)")
-        self.assertEqual(type(tx.outputs[1].conditions.left), output.SIG)
+        self.assertEqual(pypeg2.compose(tx.outputs[1].condition, output.Condition), "SIG(GNPdPNwSJAYw7ixkDeibo3YpdELgLmrZ2Q86HF4cyg92)")
+        self.assertEqual(type(tx.outputs[1].condition.left), output.SIG)
         self.assertEqual(tx.signatures[0], "XDQeEMcJDd+XVGaFIZc8d4kKRJgsPuWAPVNG5UKNk8mDZx2oE1kTP/hbxiFx6yDouBELCswuf/X6POK9ES7JCA==")
 
     def test_fromraw(self):
@@ -283,13 +283,13 @@ class Test_Transaction(unittest.TestCase):
 
         self.assertEqual(tx.outputs[0].amount, 120)
         self.assertEqual(tx.outputs[0].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
+        self.assertEqual(pypeg2.compose(tx.outputs[0].condition, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
         self.assertEqual(tx.outputs[1].amount, 146)
         self.assertEqual(tx.outputs[1].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
+        self.assertEqual(pypeg2.compose(tx.outputs[1].condition, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
         self.assertEqual(tx.outputs[2].amount, 49)
         self.assertEqual(tx.outputs[2].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
+        self.assertEqual(pypeg2.compose(tx.outputs[2].condition, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
 
         self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)")
 
@@ -350,13 +350,13 @@ class Test_Transaction(unittest.TestCase):
 
         self.assertEqual(tx.outputs[0].amount, 120)
         self.assertEqual(tx.outputs[0].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
+        self.assertEqual(pypeg2.compose(tx.outputs[0].condition, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
         self.assertEqual(tx.outputs[1].amount, 146)
         self.assertEqual(tx.outputs[1].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
+        self.assertEqual(pypeg2.compose(tx.outputs[1].condition, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
         self.assertEqual(tx.outputs[2].amount, 49)
         self.assertEqual(tx.outputs[2].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
+        self.assertEqual(pypeg2.compose(tx.outputs[2].condition, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
 
         self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)")
 
@@ -419,13 +419,13 @@ class Test_Transaction(unittest.TestCase):
 
         self.assertEqual(tx.outputs[0].amount, 120)
         self.assertEqual(tx.outputs[0].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
+        self.assertEqual(pypeg2.compose(tx.outputs[0].condition, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)")
         self.assertEqual(tx.outputs[1].amount, 146)
         self.assertEqual(tx.outputs[1].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
+        self.assertEqual(pypeg2.compose(tx.outputs[1].condition, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)")
         self.assertEqual(tx.outputs[2].amount, 49)
         self.assertEqual(tx.outputs[2].base, 2)
-        self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
+        self.assertEqual(pypeg2.compose(tx.outputs[2].condition, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(8FAA0ED653CA4D2C1156D511F0D0036F5168ABA4DAC2929676D279C8A2A12E36))")
 
         self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)")
 
-- 
GitLab