diff --git a/src/sakia/data/entities/__init__.py b/src/sakia/data/entities/__init__.py index 5e0ff2bb2c1fc4e67662af07b00c59a83ac2d217..aa9caf41c5250a3b902d4f9100db8e9cd74f97db 100644 --- a/src/sakia/data/entities/__init__.py +++ b/src/sakia/data/entities/__init__.py @@ -1,3 +1,4 @@ from .identity import Identity from .community import Community from .certification import Certification +from .transaction import Transaction diff --git a/src/sakia/data/entities/transaction.py b/src/sakia/data/entities/transaction.py new file mode 100644 index 0000000000000000000000000000000000000000..0b59ffc8bed3aadb2e4c9e1517fc099f3ccd6790 --- /dev/null +++ b/src/sakia/data/entities/transaction.py @@ -0,0 +1,18 @@ +import attr +from duniterpy.documents import block_uid + + +@attr.s() +class Transaction: + currency = attr.ib(convert=str, cmp=False) + sha_hash = attr.ib(convert=str) + written_on = attr.ib(convert=block_uid, cmp=False) + blockstamp = attr.ib(convert=block_uid, cmp=False) + timestamp = attr.ib(convert=int, cmp=False) + signature = attr.ib(convert=str, cmp=False) + issuer = attr.ib(convert=str, cmp=False) + receiver = attr.ib(convert=str, cmp=False) + amount = attr.ib(convert=int, cmp=False) + amount_base = attr.ib(convert=int, cmp=False) + comment = attr.ib(convert=str, cmp=False) + txid = attr.ib(convert=int, cmp=False) diff --git a/src/sakia/data/repositories/__init__.py b/src/sakia/data/repositories/__init__.py index 2a4b6e60aa3faa28acb6c0f1cf9ff8cf7a5605fe..99e7ed63e131f27b8fae7cc52005cd0f23cb3596 100644 --- a/src/sakia/data/repositories/__init__.py +++ b/src/sakia/data/repositories/__init__.py @@ -1,4 +1,5 @@ from .identities import IdentitiesRepo from .communities import CommunitiesRepo from .meta import MetaDatabase -from .certifications import CertificationsRepo \ No newline at end of file +from .certifications import CertificationsRepo +from .transactions import TransactionsRepo diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index dc59befef150511aaf392a627cf99462ccc26d05..c29abac18f5e5878b8d12c34703aa02e21f6177a 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -37,7 +37,9 @@ CREATE TABLE IF NOT EXISTS communities( currency VARCHAR(30), PRIMARY KEY (currency) ); --- IDENTITY TABLE + + +-- CERTIFICATIONS TABLE CREATE TABLE IF NOT EXISTS certifications( currency VARCHAR(30), certifier VARCHAR(50), @@ -48,3 +50,21 @@ CREATE TABLE IF NOT EXISTS certifications( written_on VARCHAR(100), PRIMARY KEY (currency, certifier, certified, blockstamp) ); + +-- TRANSACTIONS TABLE + +CREATE TABLE IF NOT EXISTS transactions( + currency VARCHAR(30), + sha_hash VARCHAR(50), + written_on VARCHAR(100), + blockstamp VARCHAR(100), + ts INT, + signature VARCHAR(100), + issuer VARCHAR(50), + receiver VARCHAR(50), + amount INT, + amountbase INT, + comment VARCHAR(255), + txid INT, + PRIMARY KEY (sha_hash) + ); diff --git a/src/sakia/data/repositories/transactions.py b/src/sakia/data/repositories/transactions.py new file mode 100644 index 0000000000000000000000000000000000000000..39e42db0c3a254ea00993743b9e195f15005a27d --- /dev/null +++ b/src/sakia/data/repositories/transactions.py @@ -0,0 +1,98 @@ +import attr + +from ..entities import Transaction + + +@attr.s(frozen=True) +class TransactionsRepo: + """The repository for Communities entities. + """ + _conn = attr.ib() # :type sqlite3.Connection + _primary_keys = (Transaction.sha_hash,) + + def insert(self, transaction): + """ + Commit a transaction to the database + :param sakia.data.entities.Transaction transaction: the transaction to commit + """ + with self._conn: + transaction_tuple = attr.astuple(transaction) + values = ",".join(['?'] * len(transaction_tuple)) + self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple) + + def update(self, transaction): + """ + Update an existing transaction in the database + :param sakia.data.entities.Transaction transaction: the transaction to update + """ + with self._conn: + updated_fields = attr.astuple(transaction, filter=attr.filters.exclude(*TransactionsRepo._primary_keys)) + where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys)) + self._conn.execute("""UPDATE transactions SET + currency=?, + written_on=?, + blockstamp=?, + ts=?, + signature=?, + issuer = ?, + receiver = ?, + amount = ?, + amountbase = ?, + comment = ?, + txid = ? + WHERE + sha_hash=?""", + updated_fields + where_fields) + + def get_one(self, **search): + """ + Get an existing transaction in the database + :param dict search: the criterions of the lookup + :rtype: sakia.data.entities.Transaction + """ + with self._conn: + filters = [] + values = [] + for k, v in search.items(): + filters.append("{k}=?".format(k=k)) + values.append(v) + + request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters)) + + c = self._conn.execute(request, tuple(values)) + data = c.fetchone() + if data: + return Transaction(*data) + + def get_all(self, **search): + """ + Get all existing transaction in the database corresponding to the search + :param dict search: the criterions of the lookup + :rtype: sakia.data.entities.Transaction + """ + with self._conn: + filters = [] + values = [] + for k, v in search.items(): + value = v + filters.append("{key} = ?".format(key=k)) + values.append(value) + + request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters)) + + c = self._conn.execute(request, tuple(values)) + datas = c.fetchall() + if datas: + return [Transaction(*data) for data in datas] + return [] + + def drop(self, transaction): + """ + Drop an existing transaction from the database + :param sakia.data.entities.Transaction transaction: the transaction to update + """ + with self._conn: + where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys)) + self._conn.execute("""DELETE FROM transactions + WHERE + sha_hash=?""", where_fields) diff --git a/src/sakia/tests/unit/data/test_transactions_repo.py b/src/sakia/tests/unit/data/test_transactions_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..7b9a283fcfbdeaae6dbfafa05832612e6ce1c2fe --- /dev/null +++ b/src/sakia/tests/unit/data/test_transactions_repo.py @@ -0,0 +1,110 @@ +from sakia.data.repositories import TransactionsRepo, MetaDatabase +from sakia.data.entities import Transaction +from duniterpy.documents import BlockUID +import unittest +import sqlite3 + + +class TestTransactionsRepo(unittest.TestCase): + def setUp(self): + sqlite3.register_adapter(BlockUID, str) + sqlite3.register_adapter(bool, int) + sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v))) + self.con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES) + + def tearDown(self): + self.con.close() + + def test_add_get_drop_transaction(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + transactions_repo = TransactionsRepo(self.con) + transactions_repo.insert(Transaction("testcurrency", + "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365", + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 1473108382, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + 1565, + 1, + "", + 0)) + transaction = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365") + self.assertEqual(transaction.currency, "testcurrency") + self.assertEqual(transaction.sha_hash, "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365") + self.assertEqual(transaction.written_on.number, 20) + self.assertEqual(transaction.written_on.sha_hash, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67") + self.assertEqual(transaction.blockstamp.number, 15) + self.assertEqual(transaction.blockstamp.sha_hash, "76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67") + self.assertEqual(transaction.timestamp, 1473108382) + self.assertEqual(transaction.signature, "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==") + self.assertEqual(transaction.amount, 1565) + self.assertEqual(transaction.amount_base, 1) + self.assertEqual(transaction.issuer, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ") + self.assertEqual(transaction.receiver, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn") + self.assertEqual(transaction.comment, "") + self.assertEqual(transaction.txid, 0) + + transactions_repo.drop(transaction) + transaction = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365") + self.assertIsNone(transaction) + + def test_add_get_multiple_transaction(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + transactions_repo = TransactionsRepo(self.con) + transactions_repo.insert(Transaction("testcurrency", + "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5", + "12-AAEFCCE0E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + "543-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 1473108382, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + 14, + 2, + "Test", + 2)) + transactions_repo.insert(Transaction("testcurrency", + "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365", + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 1473108382, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + 1565, + 1, + "", + 0)) + transactions = transactions_repo.get_all(currency="testcurrency") + self.assertIn("testcurrency", [t.currency for t in transactions]) + self.assertIn("7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", [t.receiver for t in transactions]) + self.assertIn("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", [t.issuer for t in transactions]) + + def test_add_update_transaction(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + transactions_repo = TransactionsRepo(self.con) + transaction = Transaction("testcurrency", + "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365", + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 1473108382, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + 1565, + 1, + "", + 0) + transactions_repo.insert(transaction) + transaction.written_on = None + transactions_repo.update(transaction) + transaction2 = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365") + self.assertEqual(transaction2.written_on, BlockUID.empty())