From f09837a9dbef0c0120979d53108b868a85078945 Mon Sep 17 00:00:00 2001
From: Moul <moul@moul.re>
Date: Fri, 29 Apr 2022 23:10:27 +0200
Subject: [PATCH] [enh] #397: Replace ClientInstance() singleton with
 @functools.lru_cache()

verify: Switch from Client(determine_endpoint) to client_instance()
which was an experiment, switched since this solution is great!
---
 silkaj/blockchain_tools.py  |  6 +++---
 silkaj/blocks.py            |  5 ++---
 silkaj/cert.py              |  6 +++---
 silkaj/commands.py          |  6 +++---
 silkaj/idty_tools.py        |  4 ++--
 silkaj/membership.py        |  6 +++---
 silkaj/money.py             |  6 +++---
 silkaj/network_tools.py     | 21 +++++----------------
 silkaj/tx_history.py        |  4 ++--
 silkaj/wot.py               |  4 ++--
 silkaj/wot_tools.py         |  6 +++---
 tests/test_membership.py    |  4 ++--
 tests/test_revocation.py    | 11 +++++------
 tests/test_unit_tx.py       |  2 +-
 tests/test_verify_blocks.py |  7 +++----
 15 files changed, 42 insertions(+), 56 deletions(-)

diff --git a/silkaj/blockchain_tools.py b/silkaj/blockchain_tools.py
index 376dca0e..67c98710 100644
--- a/silkaj/blockchain_tools.py
+++ b/silkaj/blockchain_tools.py
@@ -17,18 +17,18 @@ import functools
 
 from duniterpy.api.bma import blockchain
 
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 
 
 @functools.lru_cache(maxsize=1)
 def get_blockchain_parameters():
-    client = ClientInstance().client
+    client = client_instance()
     return client(blockchain.parameters)
 
 
 @functools.lru_cache(maxsize=1)
 def get_head_block():
-    client = ClientInstance().client
+    client = client_instance()
     return client(blockchain.current)
 
 
diff --git a/silkaj/blocks.py b/silkaj/blocks.py
index f8040db6..521287e3 100644
--- a/silkaj/blocks.py
+++ b/silkaj/blocks.py
@@ -17,13 +17,12 @@ import logging
 
 from click import INT, argument, command, progressbar
 from duniterpy.api import bma
-from duniterpy.api.client import Client
 from duniterpy.api.errors import DuniterError
 from duniterpy.documents import Block
 from duniterpy.key.verifying_key import VerifyingKey
 
 from silkaj.constants import BMA_MAX_BLOCKS_CHUNK_SIZE
-from silkaj.network_tools import determine_endpoint
+from silkaj.network_tools import client_instance
 from silkaj.tools import message_exit
 
 
@@ -36,7 +35,7 @@ If nothing specified, the whole blockchain gets verified.",
 @argument("from_block", default=0, type=INT)
 @argument("to_block", default=0, type=INT)
 def verify_blocks_signatures(from_block, to_block):
-    client = Client(determine_endpoint())
+    client = client_instance()
     to_block = check_passed_blocks_range(client, from_block, to_block)
     invalid_blocks_signatures = list()
     chunks_from = range(from_block, to_block + 1, BMA_MAX_BLOCKS_CHUNK_SIZE)
diff --git a/silkaj/cert.py b/silkaj/cert.py
index de3a3a4f..91e596aa 100644
--- a/silkaj/cert.py
+++ b/silkaj/cert.py
@@ -28,14 +28,14 @@ from silkaj.blockchain_tools import get_blockchain_parameters, get_head_block
 from silkaj.constants import ALL, DATE, SUCCESS_EXIT_STATUS
 from silkaj.crypto_tools import is_pubkey_and_check
 from silkaj.license import license_approval
-from silkaj.network_tools import ClientInstance, send_document
+from silkaj.network_tools import client_instance, send_document
 
 
 @click.command("cert", help="Send certification")
 @click.argument("uid_pubkey_to_certify")
 @click.pass_context
 def send_certification(ctx, uid_pubkey_to_certify):
-    client = ClientInstance().client
+    client = client_instance()
 
     checked_pubkey = is_pubkey_and_check(uid_pubkey_to_certify)
     if checked_pubkey:
@@ -112,7 +112,7 @@ def certification_confirmation(
 ):
     cert = list()
     cert.append(["Cert", "Issuer", "–>", "Recipient: Published: #block-hash date"])
-    client = ClientInstance().client
+    client = client_instance()
     idty_timestamp = idty_to_certify["meta"]["timestamp"]
     block_id_idty = get_block_id(idty_timestamp)
     block = client(bma.blockchain.block, block_id_idty.number)
diff --git a/silkaj/commands.py b/silkaj/commands.py
index 8b1973b5..72cad11b 100644
--- a/silkaj/commands.py
+++ b/silkaj/commands.py
@@ -26,7 +26,7 @@ from websocket._exceptions import WebSocketConnectionClosedException
 
 from silkaj.blockchain_tools import get_head_block
 from silkaj.constants import ALL, HOUR
-from silkaj.network_tools import ClientInstance, determine_endpoint
+from silkaj.network_tools import client_instance, determine_endpoint
 from silkaj.tools import get_currency_symbol
 from silkaj.wot_tools import identity_of
 
@@ -83,7 +83,7 @@ def power(nbr, pow=0):
     help="Display the current Proof of Work difficulty level to generate the next block",
 )
 def difficulties():
-    client = ClientInstance().client
+    client = client_instance()
     try:
         ws = client(bma.ws.block)
         while True:
@@ -129,7 +129,7 @@ def list_blocks(number, detailed):
     current_nbr = head_block["number"]
     if number == 0:
         number = head_block["issuersFrame"]
-    client = ClientInstance().client
+    client = client_instance()
     blocks = client(bma.blockchain.blocks, number, current_nbr - number + 1)
     issuers = list()
     issuers_dict = dict()
diff --git a/silkaj/idty_tools.py b/silkaj/idty_tools.py
index 137675f9..00031c89 100644
--- a/silkaj/idty_tools.py
+++ b/silkaj/idty_tools.py
@@ -26,7 +26,7 @@ from texttable import Texttable
 
 from silkaj import wot_tools as wt
 from silkaj.constants import ALL
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 from silkaj.tui import display_pubkey_and_checksum
 
 
@@ -34,7 +34,7 @@ def display_identity(idty: Identity):
     """
     Creates a table containing the identity infos
     """
-    client = ClientInstance().client
+    client = client_instance()
     id_table = list()
     id_table.append(["Public key", display_pubkey_and_checksum(idty.pubkey)])
     id_table.append(["User ID", idty.uid])
diff --git a/silkaj/membership.py b/silkaj/membership.py
index da36ba96..e7ce01fe 100644
--- a/silkaj/membership.py
+++ b/silkaj/membership.py
@@ -26,7 +26,7 @@ from silkaj import auth, tui, wot
 from silkaj.blockchain_tools import get_blockchain_parameters, get_head_block
 from silkaj.constants import DATE, SUCCESS_EXIT_STATUS
 from silkaj.license import license_approval
-from silkaj.network_tools import ClientInstance, send_document
+from silkaj.network_tools import client_instance, send_document
 
 
 @click.command(
@@ -54,7 +54,7 @@ def send_membership(ctx):
         license_approval(currency)
 
     # Confirmation
-    client = ClientInstance().client
+    client = client_instance()
     display_confirmation_table(identity_uid, key.pubkey, identity_block_id)
     if not dry_run and not ctx.obj["DISPLAY_DOCUMENT"]:
         tui.send_doc_confirmation("membership document for this identity")
@@ -92,7 +92,7 @@ def display_confirmation_table(identity_uid, pubkey, identity_block_id):
     between two renewals is not awaited as for the certification
     """
 
-    client = ClientInstance().client
+    client = client_instance()
 
     identities_requirements = client(bma.wot.requirements, pubkey)
     for identity_requirements in identities_requirements["identities"]:
diff --git a/silkaj/money.py b/silkaj/money.py
index b8316021..0bb9ca82 100644
--- a/silkaj/money.py
+++ b/silkaj/money.py
@@ -29,7 +29,7 @@ from silkaj.crypto_tools import (
     is_pubkey_and_check,
     validate_checksum,
 )
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 from silkaj.tools import get_currency_symbol
 from silkaj.tui import display_amount, display_pubkey_and_checksum
 
@@ -132,7 +132,7 @@ def get_amount_from_pubkey(pubkey):
 
 
 def get_sources(pubkey):
-    client = ClientInstance().client
+    client = client_instance()
     # Sources written into the blockchain
     sources = client(tx.sources, pubkey)
 
@@ -187,7 +187,7 @@ def get_sources(pubkey):
 
 @functools.lru_cache(maxsize=1)
 def get_ud_value():
-    client = ClientInstance().client
+    client = client_instance()
     blockswithud = client(blockchain.ud)
     NBlastUDblock = blockswithud["result"]["blocks"][-1]
     lastUDblock = client(blockchain.block, NBlastUDblock)
diff --git a/silkaj/network_tools.py b/silkaj/network_tools.py
index 78ce9519..df951f9a 100644
--- a/silkaj/network_tools.py
+++ b/silkaj/network_tools.py
@@ -13,6 +13,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
+import functools
 import re
 import sys
 import urllib
@@ -24,17 +25,6 @@ from duniterpy.api.client import Client
 from silkaj import constants
 
 
-def singleton(class_):
-    instances = {}
-
-    def getinstance(*args, **kwargs):
-        if class_ not in instances:
-            instances[class_] = class_(*args, **kwargs)
-        return instances[class_]
-
-    return getinstance
-
-
 def determine_endpoint():
     """
     Pass custom endpoint, parse through a regex
@@ -77,14 +67,13 @@ Expected format: {host|ipv4|[ipv6]}:{port}{/path}"
         return ep.endpoint(constants.G1_DEFAULT_ENDPOINT)
 
 
-@singleton
-class ClientInstance:
-    def __init__(self):
-        self.client = Client(determine_endpoint())
+@functools.lru_cache(maxsize=1)
+def client_instance():
+    return Client(determine_endpoint())
 
 
 def send_document(bma_path, document):
-    client = ClientInstance().client
+    client = client_instance()
     doc_name = document.__class__.__name__
     try:
         client(bma_path, document.signed_raw())
diff --git a/silkaj/tx_history.py b/silkaj/tx_history.py
index 5201ba60..238478bf 100644
--- a/silkaj/tx_history.py
+++ b/silkaj/tx_history.py
@@ -27,7 +27,7 @@ from silkaj import wot_tools as wt
 from silkaj.constants import ALL, ALL_DIGITAL
 from silkaj.crypto_tools import check_pubkey_format, validate_checksum
 from silkaj.money import amount_in_current_base, get_amount_from_pubkey, get_ud_value
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 from silkaj.tools import get_currency_symbol
 from silkaj.tui import display_pubkey_and_checksum
 
@@ -40,7 +40,7 @@ def transaction_history(pubkey, uids, full_pubkey):
     if check_pubkey_format(pubkey):
         pubkey = validate_checksum(pubkey)
 
-    client = ClientInstance().client
+    client = client_instance()
     ud_value = get_ud_value()
     currency_symbol = get_currency_symbol()
 
diff --git a/silkaj/wot.py b/silkaj/wot.py
index 92377b42..b767619b 100644
--- a/silkaj/wot.py
+++ b/silkaj/wot.py
@@ -26,7 +26,7 @@ from silkaj import wot_tools as wt
 from silkaj.blockchain_tools import get_blockchain_parameters
 from silkaj.constants import DATE
 from silkaj.crypto_tools import is_pubkey_and_check
-from silkaj.network_tools import ClientInstance, exit_on_http_error
+from silkaj.network_tools import client_instance, exit_on_http_error
 from silkaj.tools import message_exit
 from silkaj.tui import display_pubkey_and_checksum
 
@@ -56,7 +56,7 @@ def received_sent_certifications(uid_pubkey):
     get id of received and sent certifications
     display in a table the result with the numbers
     """
-    client = ClientInstance().client
+    client = client_instance()
     first_block = client(blockchain.block, 1)
     time_first_block = first_block["time"]
 
diff --git a/silkaj/wot_tools.py b/silkaj/wot_tools.py
index dcc527b4..817e3a6f 100644
--- a/silkaj/wot_tools.py
+++ b/silkaj/wot_tools.py
@@ -17,7 +17,7 @@ import sys
 
 from duniterpy.api.bma import wot
 
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 
 
 def identity_of(pubkey_uid):
@@ -26,7 +26,7 @@ def identity_of(pubkey_uid):
     Not able to get corresponding uid from a non-member identity
     Able to know if an identity is member or not
     """
-    client = ClientInstance().client
+    client = client_instance()
     try:
         return client(wot.identity_of, pubkey_uid)
     except ValueError as e:
@@ -50,7 +50,7 @@ def wot_lookup(identifier):
     Return received and sent certifications lists of matching identities
     if one identity found
     """
-    client = ClientInstance().client
+    client = client_instance()
     return (client(wot.lookup, identifier))["results"]
 
 
diff --git a/tests/test_membership.py b/tests/test_membership.py
index e9f40700..d413c00b 100644
--- a/tests/test_membership.py
+++ b/tests/test_membership.py
@@ -39,7 +39,7 @@ from silkaj import auth, blockchain_tools, membership, wot
 from silkaj.blockchain_tools import get_blockchain_parameters
 from silkaj.cli import cli
 from silkaj.constants import DATE, FAILURE_EXIT_STATUS, SUCCESS_EXIT_STATUS
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 from silkaj.tui import display_pubkey_and_checksum
 
 # Values and patches
@@ -135,7 +135,7 @@ def test_display_confirmation_table(patched_wot_requirements, monkeypatch, capsy
     monkeypatch.setattr(bma.blockchain, "parameters", patched_params)
     monkeypatch.setattr(bma.blockchain, "block", patched_block)
 
-    client = ClientInstance().client
+    client = client_instance()
     identities_requirements = client(bma.wot.requirements, pubkey)
     for identity_requirements in identities_requirements["identities"]:
         if identity_requirements["uid"] == identity_uid:
diff --git a/tests/test_revocation.py b/tests/test_revocation.py
index a520a475..223bdb05 100644
--- a/tests/test_revocation.py
+++ b/tests/test_revocation.py
@@ -24,7 +24,6 @@ import click
 import pytest
 from click.testing import CliRunner
 from duniterpy.api import bma
-from duniterpy.api.client import Client
 from duniterpy.api.errors import DuniterError
 from duniterpy.documents.identity import Identity
 from duniterpy.documents.revocation import Revocation
@@ -35,7 +34,7 @@ from patched.idty_tools import idty1, idty2, idty_block, lookup_one, lookup_two
 from silkaj import auth, blockchain_tools, idty_tools, revocation, wot
 from silkaj.cli import cli
 from silkaj.constants import FAILURE_EXIT_STATUS, SUCCESS_EXIT_STATUS
-from silkaj.network_tools import ClientInstance
+from silkaj.network_tools import client_instance
 from silkaj.tui import display_pubkey_and_checksum
 
 ### useful function ###
@@ -694,7 +693,7 @@ def test_revocation_cli_publish(
         file = revocation.REVOCATION_LOCAL_PATH
 
     # test publication
-    client = ClientInstance().client
+    client = client_instance()
     runner = CliRunner()
     with runner.isolated_filesystem():
         with open(file, "w") as f:
@@ -778,7 +777,7 @@ def test_revocation_cli_publish_send_errors(
         file = revocation.REVOCATION_LOCAL_PATH
 
     # test publication
-    client = ClientInstance().client
+    client = client_instance()
     runner = CliRunner()
     with runner.isolated_filesystem():
         with open(file, "w") as f:
@@ -862,7 +861,7 @@ def test_revocation_cli_revoke(
 
     command = display_dry_options(display, dry_run)
     command.extend(["revocation", "revoke"])
-    client = ClientInstance().client
+    client = client_instance()
 
     result = CliRunner().invoke(cli, args=command, input=user_input)
     for expect in expected:
@@ -912,7 +911,7 @@ def test_revocation_cli_revoke_errors(display, user_input, doc, expected, monkey
 
     command = display_dry_options(display, False)
     command.extend(["revocation", "revoke"])
-    client = ClientInstance().client
+    client = client_instance()
 
     result = CliRunner().invoke(cli, args=command, input=user_input)
     for expect in expected:
diff --git a/tests/test_unit_tx.py b/tests/test_unit_tx.py
index 2d3620e2..ced3d49d 100644
--- a/tests/test_unit_tx.py
+++ b/tests/test_unit_tx.py
@@ -1227,7 +1227,7 @@ def test_generate_and_send_transaction(
 
     # patched functions
     monkeypatch.setattr(blockchain_tools, "get_head_block", patched_get_head_block)
-    #    monkeypatch.setattr(network_tools, "ClientInstance", patched_ClientInstance)
+    #    monkeypatch.setattr(network_tools, "client_instance", patched_client_instance)
 
     tx.generate_and_send_transaction(
         key,
diff --git a/tests/test_verify_blocks.py b/tests/test_verify_blocks.py
index 4fe34a93..3d75c959 100644
--- a/tests/test_verify_blocks.py
+++ b/tests/test_verify_blocks.py
@@ -16,7 +16,6 @@
 import pytest
 from click.testing import CliRunner
 from duniterpy.api import bma
-from duniterpy.api.client import Client
 from duniterpy.documents import Block
 
 from silkaj import cli
@@ -32,7 +31,7 @@ from silkaj.constants import (
     FAILURE_EXIT_STATUS,
     SUCCESS_EXIT_STATUS,
 )
-from silkaj.network_tools import determine_endpoint
+from silkaj.network_tools import client_instance
 
 G1_INVALID_BLOCK_SIG = 15144
 HEAD_BLOCK = 48000
@@ -47,7 +46,7 @@ def current(self):
 )
 def test_check_passed_blocks_range(from_block, to_block, capsys, monkeypatch):
     monkeypatch.setattr(bma.blockchain, "current", current)
-    client = Client(determine_endpoint)
+    client = client_instance()
     # https://medium.com/python-pandemonium/testing-sys-exit-with-pytest-10c6e5f7726f
     with pytest.raises(SystemExit) as pytest_wrapped_e:
         check_passed_blocks_range(client, from_block, to_block)
@@ -104,7 +103,7 @@ def test_get_chunk_size(from_block, to_block, chunks_from, chunk_from):
 
 @pytest.mark.parametrize("chunk_size, chunk_from", [(2, 1), (5, 10)])
 def test_get_chunks(chunk_size, chunk_from):
-    client = Client(determine_endpoint())
+    client = client_instance()
     chunk = get_chunk(client, chunk_size, chunk_from)
     assert chunk[0]["number"] + chunk_size - 1 == chunk[-1]["number"]
 
-- 
GitLab