From 0a5a54554c7ef72c8f83d635d0fc7da3be45a203 Mon Sep 17 00:00:00 2001 From: Jonathan Zernik Date: Sat, 26 Dec 2020 01:00:58 -0800 Subject: [PATCH] Use data classes instead of namedtuples (#508) * Build mypy files for protos in tox env * Include mypy check in make test * Improve building offer from remote peer download * Remove old methods from peer task --- .gitignore | 1 + Makefile | 2 + requirements.txt | 1 + setup.py | 44 ++++++++++---- squeaknode/core/offer.py | 39 ++++++------- squeaknode/node/peer_task.py | 107 ++++++++++++++--------------------- tox.ini | 5 +- 7 files changed, 102 insertions(+), 97 deletions(-) diff --git a/.gitignore b/.gitignore index 0d44bdd0..d4a006db 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,6 @@ dist/ # Ignore generated python proto files *pb2.py *pb2_grpc.py +*.pyi libs/ \ No newline at end of file diff --git a/Makefile b/Makefile index b2a6eddb..62060b7e 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,13 @@ clean: find . -name '*~' -delete find . -name '*pb2_grpc.py' -delete find . -name '*pb2.py' -delete + find . -name '*.pyi' -delete make --directory=frontend clean; test: tox tox -e codechecks + tox -e mypy codeformat: tox -e autoflake diff --git a/requirements.txt b/requirements.txt index d46f45d7..250bd4bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ argparse googleapis-common-protos grpcio grpcio-tools +mypy-protobuf psycopg2 requests SQLAlchemy diff --git a/setup.py b/setup.py index afdc491d..1b84c7ab 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,16 @@ import io +import os +import pkg_resources +import sys import setuptools.command.build_py import setuptools.command.test -# from grpc_tools.command import BuildPackageProtos from setuptools import find_packages, setup from setuptools import Command +from grpc_tools import protoc + + PACKAGE_DIRECTORIES = { '': '.', } @@ -22,13 +27,32 @@ class BuildPyCommand(setuptools.command.build_py.build_py): setuptools.command.build_py.build_py.run(self) -# class TestCommand(setuptools.command.test.test): -# """Custom test command.""" +def build_package_protos(package_root, strict_mode=False): + proto_files = [] + inclusion_root = os.path.abspath(package_root) + for root, _, files in os.walk(inclusion_root): + for filename in files: + if filename.endswith('.proto'): + proto_files.append(os.path.abspath(os.path.join(root, + filename))) -# def run(self): -# print("Running custom test command...") -# self.run_command('build_proto_modules') -# # setuptools.command.test.test.run(self) + well_known_protos_include = pkg_resources.resource_filename( + 'grpc_tools', '_proto') + + for proto_file in proto_files: + command = [ + 'grpc_tools.protoc', + '--proto_path={}'.format(inclusion_root), + '--proto_path={}'.format(well_known_protos_include), + '--python_out={}'.format(inclusion_root), + '--grpc_python_out={}'.format(inclusion_root), + '--mypy_out={}'.format(inclusion_root), + ] + [proto_file] + if protoc.main(command) != 0: + if strict_mode: + raise Exception('error: {} failed'.format(command)) + else: + sys.stderr.write('warning: {} failed'.format(command)) class BuildPackageProtos(Command): @@ -44,8 +68,9 @@ class BuildPackageProtos(Command): pass def run(self): - import grpc_tools.command - grpc_tools.command.build_package_protos('.') + # import grpc_tools.command + # grpc_tools.command.build_package_protos('.') + build_package_protos('.') setup( name="squeaknode", @@ -66,6 +91,5 @@ setup( cmdclass={ 'build_proto_modules': BuildPackageProtos, 'build_py': BuildPyCommand, - # 'test': TestCommand, }, ) diff --git a/squeaknode/core/offer.py b/squeaknode/core/offer.py index ebb7c79f..0415c76b 100644 --- a/squeaknode/core/offer.py +++ b/squeaknode/core/offer.py @@ -1,20 +1,21 @@ -from collections import namedtuple +from dataclasses import dataclass -Offer = namedtuple( - "Offer", - [ - "offer_id", - "squeak_hash", - "price_msat", - "payment_hash", - "nonce", - "payment_point", - "invoice_timestamp", - "invoice_expiry", - "payment_request", - "destination", - "node_host", - "node_port", - "peer_id", - ], -) +from typing import Optional + + +@dataclass +class Offer: + """Class for saving an offer from a remote peer.""" + offer_id: Optional[int] + squeak_hash: str + price_msat: bytes + payment_hash: str + nonce: str + payment_point: str + invoice_timestamp: int + invoice_expiry: int + payment_request: str + destination: str + node_host: str + node_port: int + peer_id: int diff --git a/squeaknode/node/peer_task.py b/squeaknode/node/peer_task.py index 0862811d..d8aa71f4 100644 --- a/squeaknode/node/peer_task.py +++ b/squeaknode/node/peer_task.py @@ -149,7 +149,45 @@ class PeerSyncTask: squeak = self._get_local_squeak(squeak_hash) # Download the buy offer - offer = self._download_buy_offer(squeak_hash) + offer_msg = self._download_offer_msg(squeak_hash) + + # Decode the payment request + pay_req = self._decode_payment_request(offer_msg.payment_request) + logger.info("Decoded payment request: {}".format(pay_req)) + + # TODO: Use the real payment point, not a fake value. + squeak_payment_point = squeak.paymentPoint + payment_point = b'' + payment_hash = bytes.fromhex(pay_req.payment_hash) + price_msat = pay_req.num_msat + destination = pay_req.destination + invoice_timestamp = pay_req.timestamp + invoice_expiry = pay_req.expiry + node_host = offer_msg.host or self.peer.host + node_port = offer_msg.port + + logger.info("price_msat: {}".format(price_msat)) + logger.info("destination: {}".format(destination)) + logger.info("invoice_timestamp: {}".format(invoice_timestamp)) + logger.info("invoice_expiry: {}".format(invoice_expiry)) + logger.info("node_host: {}".format(node_host)) + logger.info("node_port: {}".format(node_port)) + + decoded_offer = Offer( + offer_id=None, + squeak_hash=offer_msg.squeak_hash, + price_msat=price_msat, + payment_hash=payment_hash, + nonce=offer_msg.nonce, + payment_point=squeak_payment_point, + invoice_timestamp=invoice_timestamp, + invoice_expiry=invoice_expiry, + payment_request=offer_msg.payment_request, + destination=destination, + node_host=node_host, + node_port=node_port, + peer_id=self.peer.peer_id, + ) # TODO: Check the payment point # payment_point = offer.payment_point @@ -164,9 +202,6 @@ class PeerSyncTask: # ) # ) - # Get the decoded offer from the payment request string - decoded_offer = self._get_decoded_offer(offer) - # Save the offer self._save_offer(decoded_offer) @@ -232,75 +267,15 @@ class PeerSyncTask: sharing_profiles = self.squeak_db.get_sharing_profiles() return [profile.address for profile in sharing_profiles] - def _download_buy_offer(self, squeak_hash): + def _download_offer_msg(self, squeak_hash): logger.info( "Downloading buy offer for squeak hash: {}".format(squeak_hash) ) - offer_msg = self.peer_client.buy_squeak(squeak_hash) - offer = self._offer_from_msg(offer_msg) - return offer + return self.peer_client.buy_squeak(squeak_hash) def _save_offer(self, offer): logger.info("Saving offer: {}".format(offer)) self.squeak_db.insert_offer(offer) - def _offer_from_msg(self, offer_msg): - if not offer_msg: - return None - return Offer( - offer_id=None, - squeak_hash=offer_msg.squeak_hash, - price_msat=None, - payment_hash=None, - nonce=offer_msg.nonce, - payment_point=None, - invoice_timestamp=None, - invoice_expiry=None, - payment_request=offer_msg.payment_request, - destination=None, - node_host=offer_msg.host, - node_port=offer_msg.port, - peer_id=self.peer.peer_id, - ) - def _decode_payment_request(self, payment_request): return self.lightning_client.decode_pay_req(payment_request) - - def _get_decoded_offer(self, offer): - pay_req = self._decode_payment_request(offer.payment_request) - logger.info("Decoded payment request: {}".format(pay_req)) - - # TODO: Use the real payment point, not a fake value. - payment_point = b'' - payment_hash = bytes.fromhex(pay_req.payment_hash) - price_msat = pay_req.num_msat - destination = pay_req.destination - invoice_timestamp = pay_req.timestamp - invoice_expiry = pay_req.expiry - node_host = offer.node_host or self.peer.host - node_port = offer.node_port - - logger.info("price_msat: {}".format(price_msat)) - logger.info("destination: {}".format(destination)) - logger.info("invoice_timestamp: {}".format(invoice_timestamp)) - logger.info("invoice_expiry: {}".format(invoice_expiry)) - logger.info("node_host: {}".format(node_host)) - logger.info("node_port: {}".format(node_port)) - - decoded_offer = Offer( - offer_id=offer.offer_id, - squeak_hash=offer.squeak_hash, - price_msat=price_msat, - payment_hash=payment_hash, - nonce=offer.nonce, - payment_point=payment_point, - invoice_timestamp=invoice_timestamp, - invoice_expiry=invoice_expiry, - payment_request=offer.payment_request, - destination=destination, - node_host=node_host, - node_port=node_port, - peer_id=offer.peer_id, - ) - - return decoded_offer diff --git a/tox.ini b/tox.ini index fc649eec..4d2cecbd 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ deps = -rrequirements.txt passenv = MY_APP_CONFIG_FILE commands = + {envbindir}/python setup.py build_proto_modules py.test {posargs:tests} [testenv:coverage] @@ -40,8 +41,8 @@ codechecks_paths = tests itests commands = - flake8 - reorder-python-imports + flake8 {posargs:{[testenv:codechecks]codechecks_paths}} + reorder-python-imports {posargs:{[testenv:codechecks]codechecks_paths}} [testenv:black] basepython = python3.8