From 2a7a10c701f5fc62c04b3a635b0c1573dec5b1d4 Mon Sep 17 00:00:00 2001 From: Nicolai Skye Date: Wed, 5 May 2021 08:51:47 +0700 Subject: [PATCH 01/13] Adjust build files --- Dockerfile | 11 ++++++----- build.sh | 2 +- docker-compose.yml | 9 ++++----- 3 files changed, 11 insertions(+), 11 deletions(-) mode change 100644 => 100755 build.sh diff --git a/Dockerfile b/Dockerfile index edbe260..e69603d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,14 +5,15 @@ WORKDIR /app RUN apt-get update && apt-get install -y python-pip python3.7 python3-setuptools autoconf libtool pkg-config python3-dev build-essential COPY ./requirements.txt /app/requirements.txt +COPY ./exchange.py /app/exchange.py -COPY . /app +#COPY . /app # ENV AUCTION_DEBUG_MODE False -ENV FLASK_RUN_HOST=0.0.0.0 +# ENV FLASK_RUN_HOST=0.0.0.0 -EXPOSE 2222 +#EXPOSE 2222 -WORKDIR /app +RUN pip install -r requirements.txt -RUN pip install -r requirements.txt \ No newline at end of file +CMD ["python3", "exchange.py"] diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 1147d91..a5cb786 --- a/build.sh +++ b/build.sh @@ -1,3 +1,3 @@ #!/bin/bash -app="oracle-image" +app="crypto-oracle" docker build -t ${app} . diff --git a/docker-compose.yml b/docker-compose.yml index c8a3fb5..3a31bc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,14 @@ version: "3" services: - flask: - container_name: oracle - image: oracle-image + crypto-oracle: + container_name: crypto-oracle + image: crypto-oracle ports: - - 0.0.0.0:2222:2222 + - "2222:2222" volumes: - ./data/:/app/data/ hostname: "bitcoin-bridging" command: gunicorn --bind 0.0.0.0:2222 --timeout=600 exchange:app -w 1 --threads 5 - networks: default: From 10928a9a46f4c554128dc8c39d40a91fee8beadd Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 30 Aug 2021 13:16:41 +0700 Subject: [PATCH 02/13] Adjust docker files --- Dockerfile | 6 +++--- docker-compose.yml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index e69603d..b70c6ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM python:3.7 +FROM python:3.7.11-slim WORKDIR /app -RUN apt-get update && apt-get install -y python-pip python3.7 python3-setuptools autoconf libtool pkg-config python3-dev build-essential +RUN apt-get update && apt-get install COPY ./requirements.txt /app/requirements.txt COPY ./exchange.py /app/exchange.py @@ -16,4 +16,4 @@ COPY ./exchange.py /app/exchange.py RUN pip install -r requirements.txt -CMD ["python3", "exchange.py"] +CMD ["gunicorn", "--bind 0.0.0.0:2222", "--timeout=600", "exchange:app", "-w 1", "--threads 5"] diff --git a/docker-compose.yml b/docker-compose.yml index 3a31bc3..520c62b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,8 @@ version: "3" services: crypto-oracle: container_name: crypto-oracle - image: crypto-oracle + image: crypto-oracle + build: ./ ports: - "2222:2222" volumes: From 7a079517481847d4e643cd73508f16a3b1729361 Mon Sep 17 00:00:00 2001 From: nexerino Date: Tue, 31 Aug 2021 12:23:54 +0700 Subject: [PATCH 03/13] Adjust dockerfile from relative to absolute ref --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b70c6ef..d409b02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,6 @@ COPY ./exchange.py /app/exchange.py #EXPOSE 2222 -RUN pip install -r requirements.txt +RUN pip install -r /app/requirements.txt CMD ["gunicorn", "--bind 0.0.0.0:2222", "--timeout=600", "exchange:app", "-w 1", "--threads 5"] From acdb11e2408d920cdbc0cfc3d5f6011c1cc6123c Mon Sep 17 00:00:00 2001 From: nexerino Date: Tue, 31 Aug 2021 12:24:10 +0700 Subject: [PATCH 04/13] Add trailing whitespace --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bed1671..9fad2a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /venv/ __pycache__ -history.log \ No newline at end of file +history.log From e63b7a8f20546329a00ebbc647ff65222ba77fcc Mon Sep 17 00:00:00 2001 From: nexerino Date: Tue, 31 Aug 2021 12:24:31 +0700 Subject: [PATCH 05/13] Add git describe to tag the image --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index a5cb786..d604908 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,3 @@ #!/bin/bash app="crypto-oracle" -docker build -t ${app} . +docker build -t ${app}:$(git describe --always --tags --dirty --abbrev=7) . From fc8e7ccefc11c99d913441c53ac37779289275a7 Mon Sep 17 00:00:00 2001 From: nexerino Date: Tue, 31 Aug 2021 12:25:39 +0700 Subject: [PATCH 06/13] Remove fixed container name, adjust formatting Add override file to expose port --- docker-compose.override.yml | 5 +++++ docker-compose.yml | 19 +++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 docker-compose.override.yml diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..bf201cb --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,5 @@ +version: "3.7" +services: + crypto-oracle: + ports: + - "2222:2222" diff --git a/docker-compose.yml b/docker-compose.yml index 520c62b..43deb87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,17 @@ -version: "3" +version: "3.7" services: crypto-oracle: - container_name: crypto-oracle - image: crypto-oracle - build: ./ - ports: - - "2222:2222" + image: registry.digitalocean.com/biggestfan/crypto/crypto-oracle:${TAG_CRYPTO_ORACLE:-latest} + build: + context: ${CRYPTO_ORACLE_DIR:-./} + dockerfile: ./Dockerfile volumes: - ./data/:/app/data/ - hostname: "bitcoin-bridging" + networks: + - bitcoin-bridging command: gunicorn --bind 0.0.0.0:2222 --timeout=600 exchange:app -w 1 --threads 5 networks: - default: - external: - name: bitcoin-bridging + bitcoin-bridging: + external: true \ No newline at end of file From 677dcf8855d2d997fb056b146a5b04c15f92578a Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 15:04:01 +0700 Subject: [PATCH 07/13] Add initial pytest files, update gitignore --- .coveragerc | 13 +++++++++++++ .gitignore | 2 ++ requirements.txt | 4 +++- run_tests.py | 17 +++++++++++++++++ tests/__init__.py | 1 + 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 .coveragerc create mode 100644 run_tests.py create mode 100644 tests/__init__.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..30cf777 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +[run] +source = + src/ + tests/ + +branch = True +omit = + venv/ + +[report] +exclude_lines = + pragma: no cover + if __name__ == .__main__.: diff --git a/.gitignore b/.gitignore index 9fad2a2..fb0170d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /venv/ __pycache__ history.log + +.coverage \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 08026fe..7e6a134 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ flask==1.1.2 flask_restful==0.3.8 gunicorn==20.0.4 gevent==21.1.2 -requests==2.25.1 \ No newline at end of file +requests==2.25.1 +pytest==6.2.5 +coverage==5.5 diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..77e2911 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,17 @@ +import os # pragma: no cover +import subprocess # pragma: no cover + + +def main(): # pragma: no cover + os.chdir(os.path.dirname(os.path.abspath(__file__))) + + subprocess.call(["coverage", "run", "-m", "pytest", "-rw"]) + print("\n\nTests completed, checking coverage...\n\n") + + subprocess.call(["coverage", "combine", "--append"]) + subprocess.call(["coverage", "report", "-m"]) + input("\n\nPress enter to quit ") + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 From af0c650a316c0e9bb387705cff711185ba218d5d Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 15:04:25 +0700 Subject: [PATCH 08/13] Move main file to src folder, update dockerfile --- Dockerfile | 2 +- exchange.py => src/exchange.py | 47 +++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 13 deletions(-) rename exchange.py => src/exchange.py (61%) diff --git a/Dockerfile b/Dockerfile index d409b02..4ff0f8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app RUN apt-get update && apt-get install COPY ./requirements.txt /app/requirements.txt -COPY ./exchange.py /app/exchange.py +COPY ./src/exchange.py /app/exchange.py #COPY . /app diff --git a/exchange.py b/src/exchange.py similarity index 61% rename from exchange.py rename to src/exchange.py index 5cd59ae..54e5bb9 100644 --- a/exchange.py +++ b/src/exchange.py @@ -19,19 +19,20 @@ last_rate = {} # mutex = threading.Lock() bitkub_url = "https://api.bitkub.com/api/market/ticker?sym=THB_BCH" +usd_exchange_rate_url = "https://cex.io/api/ticker/BCH/USD" NEW_RESULT_INTERVAL = 10 # Seconds class Rates: # Stores current rates out of global scope def __init__(self): - self.bitkub_rates = {} + self.rates = {} - def get_rate(self): - return self.bitkub_rates + def get_rate(self, denomination): + return self.rates.get(denomination, None) - def add_rate(self, info): - self.bitkub_rates = info + def add_rate(self, denomination, info): + self.rates[denomination] = info # rates class gets passed so thread has access @@ -48,10 +49,28 @@ def rate_fetcher(rates): rates = rates try: # get current exchange rate from bitkub - result = requests.get(bitkub_url) + thb_result = requests.get(bitkub_url) + usd_result = requests.get(usd_exchange_rate_url) logging.debug("grabbing new rate:") - logging.debug(f"result: {result.json()}") - rates.add_rate(result.json()) + logging.debug(f"result: {thb_result.json()}") + + thb_json = thb_result.json() + usd_json = usd_result.json() + + thb_rate = { + "last": thb_json["THB_BCH"]["last"], + "raw": thb_json, + "from": bitkub_url + } + + usd_rate = { + "last": usd_json["last"], + "raw": usd_json, + "from": usd_exchange_rate_url + } + + rates.add_rate("thb", thb_rate) + rates.add_rate("usd", usd_rate) except Exception as err: logging.debug(f"An error occurred: {err}") pass @@ -64,10 +83,14 @@ class GetRate(Resource): def __init__(self, **kwargs): self.rates = kwargs["rates"] - def get(self, amount, denomination): + def get(self, denomination): # Format in docs - return {"thb-bch": self.rates.get_rate()} - + + rate = self.rates.get_rate(denomination) + if not rate: + return {"This denomination is not available"}, 404 + + return {"last": self.rates.get_rate()}, 200 class Exchange(Flask): # pragma: no cover @@ -99,7 +122,7 @@ def run( app = Exchange(__name__) api = Api(app) -api.add_resource(GetRate, '/api/get_rate//', resource_class_kwargs={ +api.add_resource(GetRate, '/api/get_rate/', resource_class_kwargs={ "rates": rates}) if __name__ == '__main__': From 35b637ff2ea9b95294b34978913e08348575f4ea Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 15:04:37 +0700 Subject: [PATCH 09/13] Add unit tests for get_rate --- tests/samples.py | 2 ++ tests/test_exchange.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/samples.py create mode 100644 tests/test_exchange.py diff --git a/tests/samples.py b/tests/samples.py new file mode 100644 index 0000000..8784840 --- /dev/null +++ b/tests/samples.py @@ -0,0 +1,2 @@ +thb_rate_json = {"THB_BCH":{"id":6,"last":17069.79,"lowestAsk":17062.1,"highestBid":17039.27,"percentChange":2.31,"baseVolume":542.12365819,"quoteVolume":9338020.59,"isFrozen":0,"high24hr":17630.96,"low24hr":16586.02,"change":385.57,"prevClose":17069.79,"prevOpen":16684.22}} + diff --git a/tests/test_exchange.py b/tests/test_exchange.py new file mode 100644 index 0000000..070532e --- /dev/null +++ b/tests/test_exchange.py @@ -0,0 +1,33 @@ +import pytest +import json + +from src.exchange import ( + Rates, + bitkub_url, + usd_exchange_rate_url, +) + +from tests.samples import ( + thb_rate_json, +) + + + + +def test_get_rate(): + rates = Rates() + + thb_rate = { + "last": thb_rate_json["THB_BCH"]["last"], + "raw": thb_rate_json, + "from": bitkub_url + } + + rates.add_rate("thb", thb_rate) + + assert rates.get_rate("thb") == thb_rate + +def test_denomination_not_found(): + rates = Rates() + + assert rates.get_rate("not_found") == None \ No newline at end of file From b09be6ea705d3b789bd7a98800ac85c0c5440fe2 Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 15:47:10 +0700 Subject: [PATCH 10/13] adjust rate objects, add unit tests --- src/exchange.py | 18 ++++++----- tests/test_exchange.py | 72 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/exchange.py b/src/exchange.py index 54e5bb9..abdbe39 100644 --- a/src/exchange.py +++ b/src/exchange.py @@ -36,7 +36,7 @@ def add_rate(self, denomination, info): # rates class gets passed so thread has access -def start_rate_thread(rates): +def start_rate_thread(rates): # pragma: no cover logging.debug("starting") rate_thread = threading.Thread(target=rate_fetcher, args=(rates,)) rate_thread.daemon = True @@ -44,7 +44,7 @@ def start_rate_thread(rates): return # rates class gets passed so thread has access -def rate_fetcher(rates): +def rate_fetcher(rates): # pragma: no cover while True: rates = rates try: @@ -53,20 +53,23 @@ def rate_fetcher(rates): usd_result = requests.get(usd_exchange_rate_url) logging.debug("grabbing new rate:") logging.debug(f"result: {thb_result.json()}") + logging.debug(f"result: {usd_result.json()}") thb_json = thb_result.json() usd_json = usd_result.json() thb_rate = { - "last": thb_json["THB_BCH"]["last"], + "last": str(thb_json["THB_BCH"]["last"]), "raw": thb_json, - "from": bitkub_url + "from": bitkub_url, + "timestamp": time.time() } usd_rate = { "last": usd_json["last"], "raw": usd_json, - "from": usd_exchange_rate_url + "from": usd_exchange_rate_url, + "timestamp": usd_json["timestamp"] } rates.add_rate("thb", thb_rate) @@ -85,12 +88,11 @@ def __init__(self, **kwargs): def get(self, denomination): # Format in docs - rate = self.rates.get_rate(denomination) if not rate: - return {"This denomination is not available"}, 404 + return {"message": "This denomination is not available"}, 404 - return {"last": self.rates.get_rate()}, 200 + return {"last": rate["last"]}, 200 class Exchange(Flask): # pragma: no cover diff --git a/tests/test_exchange.py b/tests/test_exchange.py index 070532e..b1691ae 100644 --- a/tests/test_exchange.py +++ b/tests/test_exchange.py @@ -1,10 +1,17 @@ import pytest import json +import unittest +from unittest import mock +from unittest.mock import mock_open + + from src.exchange import ( Rates, bitkub_url, usd_exchange_rate_url, + app, + rates ) from tests.samples import ( @@ -13,21 +20,58 @@ - -def test_get_rate(): - rates = Rates() - thb_rate = { - "last": thb_rate_json["THB_BCH"]["last"], - "raw": thb_rate_json, - "from": bitkub_url - } +class Tests: + def test_get_rate(self): + rates = Rates() + + thb_rate = { + "last": thb_rate_json["THB_BCH"]["last"], + "raw": thb_rate_json, + "from": bitkub_url + } + rates.add_rate("thb", thb_rate) + + assert rates.get_rate("thb") == thb_rate + + + def test_denomination_not_found(self): + rates = Rates() + + assert rates.get_rate("not_found") == None - rates.add_rate("thb", thb_rate) - assert rates.get_rate("thb") == thb_rate + def test_denomination_not_available(self): + + # denomination = "thb" + url = f"/api/get_rate/thb" + + flask_app = app + with flask_app.test_client(self) as test_client: + + response = test_client.get(url, content_type="application/json") + result = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 404 + assert result == {'message': 'This denomination is not available'} -def test_denomination_not_found(): - rates = Rates() - - assert rates.get_rate("not_found") == None \ No newline at end of file + def test_get_thb(self): + + # denomination = "thb" + url = f"/api/get_rate/thb" + + thb_rate = { + "last": 20000 + } + + rates.add_rate("thb", thb_rate) + + flask_app = app + with flask_app.test_client(self) as test_client: + + response = test_client.get(url, content_type="application/json") + result = json.loads(response.get_data(as_text=True)) + + assert response.status_code == 200 + assert result == {'last': 20000} + \ No newline at end of file From 91b2c56705269b7c0a6bc27d417a3a913296f08e Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 16:26:30 +0700 Subject: [PATCH 11/13] Adjust dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4ff0f8b..52de093 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ WORKDIR /app RUN apt-get update && apt-get install COPY ./requirements.txt /app/requirements.txt +RUN pip install -r /app/requirements.txt + COPY ./src/exchange.py /app/exchange.py #COPY . /app @@ -14,6 +16,4 @@ COPY ./src/exchange.py /app/exchange.py #EXPOSE 2222 -RUN pip install -r /app/requirements.txt - CMD ["gunicorn", "--bind 0.0.0.0:2222", "--timeout=600", "exchange:app", "-w 1", "--threads 5"] From d9b881554cc24f98ae9dfff272c11afe6c4d1534 Mon Sep 17 00:00:00 2001 From: nexerino Date: Mon, 27 Sep 2021 16:26:45 +0700 Subject: [PATCH 12/13] Change rate to remove decimal places --- src/exchange.py | 23 ++++++++++++++++------- tests/test_exchange.py | 6 +++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/exchange.py b/src/exchange.py index abdbe39..9bf407f 100644 --- a/src/exchange.py +++ b/src/exchange.py @@ -9,6 +9,7 @@ from flask import Flask from flask_restful import Resource, Api +from decimal import Decimal logging.basicConfig(filename='history.log', filemode='w', level=logging.DEBUG) @@ -59,21 +60,23 @@ def rate_fetcher(rates): # pragma: no cover usd_json = usd_result.json() thb_rate = { - "last": str(thb_json["THB_BCH"]["last"]), + "last": int(Decimal(str(thb_json["THB_BCH"]["last"])) * 100), + "multiplier": 100, "raw": thb_json, "from": bitkub_url, - "timestamp": time.time() + "timestamp": int(time.time()) } usd_rate = { - "last": usd_json["last"], + "last": int(Decimal(str(usd_json["last"])) * 100), + "multiplier": 100, "raw": usd_json, "from": usd_exchange_rate_url, - "timestamp": usd_json["timestamp"] + "timestamp": int(usd_json["timestamp"]) } - rates.add_rate("thb", thb_rate) - rates.add_rate("usd", usd_rate) + rates.add_rate("THB", thb_rate) + rates.add_rate("USD", usd_rate) except Exception as err: logging.debug(f"An error occurred: {err}") pass @@ -92,7 +95,13 @@ def get(self, denomination): if not rate: return {"message": "This denomination is not available"}, 404 - return {"last": rate["last"]}, 200 + result = { + "last": rate["last"], + "multiplier": rate["multiplier"], + "timestamp": rate["timestamp"] + } + + return result, 200 class Exchange(Flask): # pragma: no cover diff --git a/tests/test_exchange.py b/tests/test_exchange.py index b1691ae..1aea28e 100644 --- a/tests/test_exchange.py +++ b/tests/test_exchange.py @@ -44,7 +44,7 @@ def test_denomination_not_found(self): def test_denomination_not_available(self): # denomination = "thb" - url = f"/api/get_rate/thb" + url = f"/api/get_rate/THB" flask_app = app with flask_app.test_client(self) as test_client: @@ -58,13 +58,13 @@ def test_denomination_not_available(self): def test_get_thb(self): # denomination = "thb" - url = f"/api/get_rate/thb" + url = f"/api/get_rate/THB" thb_rate = { "last": 20000 } - rates.add_rate("thb", thb_rate) + rates.add_rate("THB", thb_rate) flask_app = app with flask_app.test_client(self) as test_client: From 890660d0edc9808d18fed4d93062239f526976e4 Mon Sep 17 00:00:00 2001 From: nexerino Date: Tue, 28 Sep 2021 11:28:38 +0700 Subject: [PATCH 13/13] Adjust unit tests to account for multiplier & time --- tests/test_exchange.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_exchange.py b/tests/test_exchange.py index 1aea28e..842b33c 100644 --- a/tests/test_exchange.py +++ b/tests/test_exchange.py @@ -61,7 +61,9 @@ def test_get_thb(self): url = f"/api/get_rate/THB" thb_rate = { - "last": 20000 + "last": 20000, + "multiplier": 100, + "timestamp": 2000000 } rates.add_rate("THB", thb_rate) @@ -73,5 +75,5 @@ def test_get_thb(self): result = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 - assert result == {'last': 20000} + assert result == thb_rate \ No newline at end of file