From b2c04d75c3a817601c0db3d60e3fce54e49e0e2b Mon Sep 17 00:00:00 2001
From: Vincent Texier <vit@free.fr>
Date: Sat, 24 Sep 2016 11:26:50 +0200
Subject: [PATCH] merge community in blockchain repository

add paginate and sort in get_all
---
 src/sakia/data/entities/__init__.py           |   3 +-
 src/sakia/data/entities/blockchain.py         |  38 +++++++
 src/sakia/data/entities/community.py          |  42 -------
 src/sakia/data/processors/communities.py      |  16 ---
 src/sakia/data/repositories/__init__.py       |   1 -
 src/sakia/data/repositories/blockchains.py    |  54 ++++++---
 src/sakia/data/repositories/communities.py    | 102 -----------------
 src/sakia/data/repositories/meta.sql          |  10 +-
 .../tests/unit/data/test_blockchains_repo.py  | 107 ++++++++++++++++--
 9 files changed, 177 insertions(+), 196 deletions(-)
 delete mode 100644 src/sakia/data/entities/community.py
 delete mode 100644 src/sakia/data/processors/communities.py
 delete mode 100644 src/sakia/data/repositories/communities.py

diff --git a/src/sakia/data/entities/__init__.py b/src/sakia/data/entities/__init__.py
index 99cf36c9..40b3174b 100644
--- a/src/sakia/data/entities/__init__.py
+++ b/src/sakia/data/entities/__init__.py
@@ -1,6 +1,5 @@
 from .identity import Identity
-from .community import Community
-from .blockchain import Blockchain
+from .blockchain import Blockchain, BlockchainParameters
 from .certification import Certification
 from .transaction import Transaction
 from .node import Node
diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py
index bdf7bf2c..72d44d38 100644
--- a/src/sakia/data/entities/blockchain.py
+++ b/src/sakia/data/entities/blockchain.py
@@ -2,8 +2,46 @@ import attr
 from duniterpy.documents import block_uid, BlockUID
 
 
+@attr.s()
+class BlockchainParameters:
+    # The decimal percent growth of the UD every [dt] period
+    c = attr.ib(convert=float, default=0, cmp=False)
+    # Time period between two UD in seconds
+    dt = attr.ib(convert=int, default=0, cmp=False)
+    # UD(0), i.e. initial Universal Dividend
+    ud0 = attr.ib(convert=int, default=0, cmp=False)
+    # Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero
+    sig_period = attr.ib(convert=int, default=0, cmp=False)
+    # Maximum quantity of active certifications made by member
+    sig_stock = attr.ib(convert=int, default=0, cmp=False)
+    # Maximum delay in seconds a certification can wait before being expired for non-writing
+    sig_window = attr.ib(convert=int, default=0, cmp=False)
+    # Maximum age of a active signature (in seconds)
+    sig_validity = attr.ib(convert=int, default=0, cmp=False)
+    # Minimum quantity of signatures to be part of the WoT
+    sig_qty = attr.ib(convert=int, default=0, cmp=False)
+    # Minimum decimal percent of sentries to reach to match the distance rule
+    xpercent = attr.ib(convert=float, default=0, cmp=False)
+    # Maximum age of an active membership( in seconds)
+    ms_validity = attr.ib(convert=int, default=0, cmp=False)
+    # Maximum distance between each WoT member and a newcomer
+    step_max = attr.ib(convert=int, default=0, cmp=False)
+    # Number of blocks used for calculating median time
+    median_time_blocks = attr.ib(convert=int, default=0, cmp=False)
+    # The average time for writing 1 block (wished time) in seconds
+    avg_gen_time = attr.ib(convert=int, default=0, cmp=False)
+    # The number of blocks required to evaluate again PoWMin value
+    dt_diff_eval = attr.ib(convert=int, default=0, cmp=False)
+    # The number of previous blocks to check for personalized difficulty
+    blocks_rot = attr.ib(convert=int, default=0, cmp=False)
+    # The decimal percent of previous issuers to reach for personalized difficulty
+    percent_rot = attr.ib(convert=float, default=0, cmp=False)
+
+
 @attr.s()
 class Blockchain:
+    # Parameters in block 0
+    parameters = attr.ib(default=BlockchainParameters())
     # block number and hash
     current_buid = attr.ib(convert=block_uid, default=BlockUID.empty())
     # Number of members
diff --git a/src/sakia/data/entities/community.py b/src/sakia/data/entities/community.py
deleted file mode 100644
index 2e483c09..00000000
--- a/src/sakia/data/entities/community.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import attr
-
-
-@attr.s()
-class Community:
-    # The decimal percent growth of the UD every [dt] period
-    c = attr.ib(convert=float, default=0, cmp=False)
-    # Time period between two UD in seconds
-    dt = attr.ib(convert=int, default=0, cmp=False)
-    # UD(0), i.e. initial Universal Dividend
-    ud0 = attr.ib(convert=int, default=0, cmp=False)
-    # Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero
-    sig_period = attr.ib(convert=int, default=0, cmp=False)
-    # Maximum quantity of active certifications made by member
-    sig_stock = attr.ib(convert=int, default=0, cmp=False)
-    # Maximum delay in seconds a certification can wait before being expired for non-writing
-    sig_window = attr.ib(convert=int, default=0, cmp=False)
-    # Maximum age of a active signature (in seconds)
-    sig_validity = attr.ib(convert=int, default=0, cmp=False)
-    # Minimum quantity of signatures to be part of the WoT
-    sig_qty = attr.ib(convert=int, default=0, cmp=False)
-    # Minimum decimal percent of sentries to reach to match the distance rule
-    xpercent = attr.ib(convert=float, default=0, cmp=False)
-    # Maximum age of an active membership( in seconds)
-    ms_validity = attr.ib(convert=int, default=0, cmp=False)
-    # Maximum distance between each WoT member and a newcomer
-    step_max = attr.ib(convert=int, default=0, cmp=False)
-    # Number of blocks used for calculating median time
-    median_time_blocks = attr.ib(convert=int, default=0, cmp=False)
-    # The average time for writing 1 block (wished time) in seconds
-    avg_gen_time = attr.ib(convert=int, default=0, cmp=False)
-    # The number of blocks required to evaluate again PoWMin value
-    dt_diff_eval = attr.ib(convert=int, default=0, cmp=False)
-    # The number of previous blocks to check for personalized difficulty
-    blocks_rot = attr.ib(convert=int, default=0, cmp=False)
-    # The decimal percent of previous issuers to reach for personalized difficulty
-    percent_rot = attr.ib(convert=float, default=0, cmp=False)
-    # Currency name
-    currency = attr.ib(convert=str, default="", cmp=False)
-
-
-
diff --git a/src/sakia/data/processors/communities.py b/src/sakia/data/processors/communities.py
deleted file mode 100644
index b9ad62a4..00000000
--- a/src/sakia/data/processors/communities.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import attr
-
-
-@attr.s
-class CommunityProcessor:
-    _repo = attr.ib()  # :type sakia.data.repositories.CommunitiesRepo
-
-    async def get_from_currency(self, currency):
-        """
-        Get the community of a currency
-
-        :param currency:
-        :rtype: sakia.data.entities.Community
-        """
-        return self._repo.get_one(currency=currency)
-
diff --git a/src/sakia/data/repositories/__init__.py b/src/sakia/data/repositories/__init__.py
index b047145e..a7e8f366 100644
--- a/src/sakia/data/repositories/__init__.py
+++ b/src/sakia/data/repositories/__init__.py
@@ -1,5 +1,4 @@
 from .identities import IdentitiesRepo
-from .communities import CommunitiesRepo
 from .blockchains import BlockchainsRepo
 from .meta import MetaDatabase
 from .certifications import CertificationsRepo
diff --git a/src/sakia/data/repositories/blockchains.py b/src/sakia/data/repositories/blockchains.py
index cede12dd..055deab2 100644
--- a/src/sakia/data/repositories/blockchains.py
+++ b/src/sakia/data/repositories/blockchains.py
@@ -1,6 +1,8 @@
+from typing import List
+
 import attr
 
-from ..entities import Blockchain
+from ..entities import Blockchain, BlockchainParameters
 
 
 @attr.s(frozen=True)
@@ -16,7 +18,8 @@ class BlockchainsRepo:
         :param sakia.data.entities.Blockchain blockchain: the blockchain to commit
         """
         with self._conn:
-            blockchain_tuple = attr.astuple(blockchain)
+            blockchain_tuple = attr.astuple(blockchain.parameters) \
+                               + attr.astuple(blockchain, filter=attr.filters.exclude(Blockchain.parameters))
             values = ",".join(['?'] * len(blockchain_tuple))
             self._conn.execute("INSERT INTO blockchains VALUES ({0})".format(values), blockchain_tuple)
 
@@ -26,7 +29,8 @@ class BlockchainsRepo:
         :param sakia.data.entities.Blockchain blockchain: the blockchain to update
         """
         with self._conn:
-            updated_fields = attr.astuple(blockchain, filter=attr.filters.exclude(*BlockchainsRepo._primary_keys))
+            updated_fields = attr.astuple(blockchain, filter=attr.filters.exclude(
+                Blockchain.parameters, *BlockchainsRepo._primary_keys))
             where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys))
             self._conn.execute("""UPDATE blockchains SET
                               current_buid=?,
@@ -58,29 +62,49 @@ class BlockchainsRepo:
             c = self._conn.execute(request, tuple(values))
             data = c.fetchone()
             if data:
-                return Blockchain(*data)
+                return Blockchain(BlockchainParameters(*data[:15]), *data[16:])
 
-    def get_all(self, **search):
+    def get_all(self, offset=0, limit=1000, sort_by="currency", sort_order="ASC", **search) -> List[Blockchain]:
         """
         Get all existing blockchain in the database corresponding to the search
+        :param int offset: offset in results to paginate
+        :param int limit: limit results to paginate
+        :param str sort_by: column name to sort by
+        :param str sort_order: sort order ASC or DESC
         :param dict search: the criterions of the lookup
-        :rtype: sakia.data.entities.Blockchain
+        :rtype: [sakia.data.entities.Blockchain]
         """
         with self._conn:
             filters = []
             values = []
-            for k, v in search.items():
-                operator = "LIKE" if k == "currency" else "="
-                value = "%{value}%".format(value=v) if k == "currency" else v
-                filters.append("{key} {operator} ?".format(key=k, operator=operator))
-                values.append(value)
+            if search:
+                for k, v in search.items():
+                    filters.append("{k}=?".format(k=k))
+                    values.append(v)
 
-            request = "SELECT * FROM blockchains WHERE {filters}".format(filters=" AND ".join(filters))
-
-            c = self._conn.execute(request, tuple(values))
+                request = """SELECT * FROM blockchains WHERE {filters}
+                              ORDER BY {sort_by} {sort_order}
+                              LIMIT {limit} OFFSET {offset}""".format(
+                    filters=" AND ".join(filters),
+                    offset=offset,
+                    limit=limit,
+                    sort_by=sort_by,
+                    sort_order=sort_order
+                )
+                c = self._conn.execute(request, tuple(values))
+            else:
+                request = """SELECT * FROM blockchains
+                              ORDER BY {sort_by} {sort_order}
+                              LIMIT {limit} OFFSET {offset}""".format(
+                    offset=offset,
+                    limit=limit,
+                    sort_by=sort_by,
+                    sort_order=sort_order
+                )
+                c = self._conn.execute(request)
             datas = c.fetchall()
             if datas:
-                return [Blockchain(*data) for data in datas]
+                return [Blockchain(BlockchainParameters(*data[:15]), *data[16:]) for data in datas]
         return []
 
     def drop(self, blockchain):
diff --git a/src/sakia/data/repositories/communities.py b/src/sakia/data/repositories/communities.py
deleted file mode 100644
index d2f21a1b..00000000
--- a/src/sakia/data/repositories/communities.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import attr
-
-from ..entities import Community
-
-
-@attr.s(frozen=True)
-class CommunitiesRepo:
-    """The repository for Communities entities.
-    """
-    _conn = attr.ib()  # :type sqlite3.Connection
-    _primary_keys = (Community.currency,)
-
-    def insert(self, community):
-        """
-        Commit a community to the database
-        :param sakia.data.entities.Community community: the community to commit
-        """
-        with self._conn:
-            community_tuple = attr.astuple(community)
-            values = ",".join(['?'] * len(community_tuple))
-            self._conn.execute("INSERT INTO communities VALUES ({0})".format(values), community_tuple)
-
-    def update(self, community):
-        """
-        Update an existing community in the database
-        :param sakia.data.entities.Community community: the community to update
-        """
-        with self._conn:
-            updated_fields = attr.astuple(community, filter=attr.filters.exclude(*CommunitiesRepo._primary_keys))
-            where_fields = attr.astuple(community, filter=attr.filters.include(*CommunitiesRepo._primary_keys))
-            self._conn.execute("""UPDATE communities SET
-                              c=?,
-                              dt=?,
-                              ud0=?,
-                              sig_period=?,
-                              sig_stock=?,
-                              sig_window=?,
-                              sig_validity=?,
-                              sig_qty=?,
-                              xpercent=?,
-                              ms_validity=?,
-                              step_max=?,
-                              median_time_blocks=?,
-                              avg_gen_time=?,
-                              dt_diff_eval=?,
-                              blocks_rot=?,
-                              percent_rot=?
-                               WHERE
-                              currency=?""",
-                               updated_fields + where_fields)
-
-    def get_one(self, **search):
-        """
-        Get an existing community in the database
-        :param dict search: the criterions of the lookup
-        :rtype: sakia.data.entities.Community
-        """
-        with self._conn:
-            filters = []
-            values = []
-            for k, v in search.items():
-                filters.append("{k}=?".format(k=k))
-                values.append(v)
-
-            request = "SELECT * FROM communities WHERE {filters}".format(filters=" AND ".join(filters))
-
-            c = self._conn.execute(request, tuple(values))
-            data = c.fetchone()
-            if data:
-                return Community(*data)
-
-    def get_all(self, **search):
-        """
-        Get all existing community in the database corresponding to the search
-        :param dict search: the criterions of the lookup
-        :rtype: sakia.data.entities.Community
-        """
-        with self._conn:
-            filters = []
-            values = []
-            for k, v in search.items():
-                operator = "LIKE" if k == "currency" else "="
-                value = "%{value}%".format(value=v) if k == "currency" else v
-                filters.append("{key} {operator} ?".format(key=k, operator=operator))
-                values.append(value)
-
-            request = "SELECT * FROM communities WHERE {filters}".format(filters=" AND ".join(filters))
-
-            c = self._conn.execute(request, tuple(values))
-            datas = c.fetchall()
-            if datas:
-                return [Community(*data) for data in datas]
-        return []
-
-    def drop(self, community):
-        """
-        Drop an existing community from the database
-        :param sakia.data.entities.Community community: the community to update
-        """
-        with self._conn:
-            where_fields = attr.astuple(community, filter=attr.filters.include(*CommunitiesRepo._primary_keys))
-            self._conn.execute("DELETE FROM communities WHERE currency=?", where_fields)
diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql
index 31e6af6c..f30c866d 100644
--- a/src/sakia/data/repositories/meta.sql
+++ b/src/sakia/data/repositories/meta.sql
@@ -16,8 +16,8 @@ CREATE TABLE IF NOT EXISTS identities(
                                PRIMARY KEY (currency, pubkey, uid, blockstamp)
                                );
 
--- COMMUNITIES TABLE
-CREATE TABLE IF NOT EXISTS communities (
+-- BLOCKCHAIN TABLE
+CREATE TABLE IF NOT EXISTS blockchains (
   c                  FLOAT(1, 6),
   dt                 INT,
   ud0                INT,
@@ -34,12 +34,6 @@ CREATE TABLE IF NOT EXISTS communities (
   dt_diff_eval       INT,
   blocks_rot         INT,
   percent_rot        FLOAT(1, 6),
-  currency           VARCHAR(30),
-  PRIMARY KEY (currency)
-);
-
--- BLOCKCHAIN TABLE
-CREATE TABLE IF NOT EXISTS blockchains (
   current_buid INT,
   nb_members    INT,
   current_mass  INT,
diff --git a/src/sakia/tests/unit/data/test_blockchains_repo.py b/src/sakia/tests/unit/data/test_blockchains_repo.py
index c52ef722..8f831fd0 100644
--- a/src/sakia/tests/unit/data/test_blockchains_repo.py
+++ b/src/sakia/tests/unit/data/test_blockchains_repo.py
@@ -3,7 +3,7 @@ import unittest
 
 from duniterpy.documents import BlockUID
 
-from sakia.data.entities import Blockchain
+from sakia.data.entities import Blockchain, BlockchainParameters
 from sakia.data.repositories import BlockchainsRepo, MetaDatabase
 
 
@@ -23,6 +23,23 @@ class TestBlockchainsRepo(unittest.TestCase):
         meta_repo.upgrade_database()
         blockchains_repo = BlockchainsRepo(self.con)
         blockchains_repo.insert(Blockchain(
+            BlockchainParameters(
+                0.1,
+                86400,
+                100000,
+                10800,
+                40,
+                2629800,
+                31557600,
+                1,
+                0.9,
+                604800,
+                5,
+                12,
+                300,
+                25,
+                10,
+                0.66),
             "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
             10,
             1000000,
@@ -33,6 +50,23 @@ class TestBlockchainsRepo(unittest.TestCase):
             "testcurrency"
         ))
         blockchain = blockchains_repo.get_one(currency="testcurrency")
+        self.assertEqual(blockchain.parameters, BlockchainParameters(
+                0.1,
+                86400,
+                100000,
+                10800,
+                40,
+                2629800,
+                31557600,
+                1,
+                0.9,
+                604800,
+                5,
+                12,
+                300,
+                25,
+                10,
+                0.66))
         self.assertEqual(blockchain.currency, "testcurrency")
         self.assertEqual(blockchain.current_buid, BlockUID(20,
                                                            "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
@@ -49,6 +83,23 @@ class TestBlockchainsRepo(unittest.TestCase):
         meta_repo.upgrade_database()
         blockchains_repo = BlockchainsRepo(self.con)
         blockchains_repo.insert(Blockchain(
+            BlockchainParameters(
+                0.1,
+                86400,
+                100000,
+                10800,
+                40,
+                2629800,
+                31557600,
+                1,
+                0.9,
+                604800,
+                5,
+                12,
+                300,
+                25,
+                10,
+                0.66),
             "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
             10,
             1000000,
@@ -57,9 +108,25 @@ class TestBlockchainsRepo(unittest.TestCase):
             0,
             999999,
             "testcurrency"
-        )
-        )
+        ))
         blockchains_repo.insert(Blockchain(
+            BlockchainParameters(
+                0.1,
+                86400 * 365,
+                100000,
+                10800,
+                40,
+                2629800,
+                31557600,
+                1,
+                0.9,
+                604800,
+                5,
+                12,
+                300,
+                25,
+                10,
+                0.66),
             "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
             20,
             1000000,
@@ -68,14 +135,17 @@ class TestBlockchainsRepo(unittest.TestCase):
             0,
             999999,
             "testcurrency2"
-        )
-        )
+        ))
+
+        blockchains = blockchains_repo.get_all()
+        # result sorted by currency name by default
+        self.assertEquals(86400, blockchains[0].parameters.dt)
+        self.assertEquals("testcurrency", blockchains[0].currency)
+        self.assertEquals(10, blockchains[0].nb_members)
 
-        blockchains = blockchains_repo.get_all(currency="testcurrency")
-        self.assertIn("testcurrency", [i.currency for i in blockchains])
-        self.assertIn("testcurrency2", [i.currency for i in blockchains])
-        self.assertIn(10, [i.nb_members for i in blockchains])
-        self.assertIn(20, [i.nb_members for i in blockchains])
+        self.assertEquals(86400*365, blockchains[1].parameters.dt)
+        self.assertEquals("testcurrency2", blockchains[1].currency)
+        self.assertEquals(20, blockchains[1].nb_members)
 
     def test_add_update_blockchain(self):
         meta_repo = MetaDatabase(self.con)
@@ -83,6 +153,23 @@ class TestBlockchainsRepo(unittest.TestCase):
         meta_repo.upgrade_database()
         blockchains_repo = BlockchainsRepo(self.con)
         blockchain = Blockchain(
+            BlockchainParameters(
+                0.1,
+                86400,
+                100000,
+                10800,
+                40,
+                2629800,
+                31557600,
+                1,
+                0.9,
+                604800,
+                5,
+                12,
+                300,
+                25,
+                10,
+                0.66),
             "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
             10,
             1000000,
-- 
GitLab