Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[run]
source =
src/
tests/

branch = True
omit =
venv/

[report]
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/venv/
__pycache__
history.log
history.log

.coverage
17 changes: 9 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
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
RUN pip install -r /app/requirements.txt

COPY . /app
COPY ./src/exchange.py /app/exchange.py

# ENV AUCTION_DEBUG_MODE False
ENV FLASK_RUN_HOST=0.0.0.0
#COPY . /app

EXPOSE 2222
# ENV AUCTION_DEBUG_MODE False
# ENV FLASK_RUN_HOST=0.0.0.0

WORKDIR /app
#EXPOSE 2222

RUN pip install -r requirements.txt
CMD ["gunicorn", "--bind 0.0.0.0:2222", "--timeout=600", "exchange:app", "-w 1", "--threads 5"]
4 changes: 2 additions & 2 deletions build.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
app="oracle-image"
docker build -t ${app} .
app="crypto-oracle"
docker build -t ${app}:$(git describe --always --tags --dirty --abbrev=7) .
5 changes: 5 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.7"
services:
crypto-oracle:
ports:
- "2222:2222"
21 changes: 10 additions & 11 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
version: "3"
version: "3.7"
services:
flask:
container_name: oracle
image: oracle-image
ports:
- 0.0.0.0:2222:2222
crypto-oracle:
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

4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
requests==2.25.1
pytest==6.2.5
coverage==5.5
17 changes: 17 additions & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
@@ -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()
62 changes: 48 additions & 14 deletions exchange.py → src/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -19,39 +20,63 @@
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
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
rate_thread.start()
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:
# 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()}")
logging.debug(f"result: {usd_result.json()}")

thb_json = thb_result.json()
usd_json = usd_result.json()

thb_rate = {
"last": int(Decimal(str(thb_json["THB_BCH"]["last"])) * 100),
"multiplier": 100,
"raw": thb_json,
"from": bitkub_url,
"timestamp": int(time.time())
}

usd_rate = {
"last": int(Decimal(str(usd_json["last"])) * 100),
"multiplier": 100,
"raw": usd_json,
"from": usd_exchange_rate_url,
"timestamp": int(usd_json["timestamp"])
}

rates.add_rate("THB", thb_rate)
rates.add_rate("USD", usd_rate)
except Exception as err:
logging.debug(f"An error occurred: {err}")
pass
Expand All @@ -64,10 +89,19 @@ 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 {"message": "This denomination is not available"}, 404

result = {
"last": rate["last"],
"multiplier": rate["multiplier"],
"timestamp": rate["timestamp"]
}

return result, 200


class Exchange(Flask): # pragma: no cover
Expand Down Expand Up @@ -99,7 +133,7 @@ def run(
app = Exchange(__name__)
api = Api(app)

api.add_resource(GetRate, '/api/get_rate/<amount>/<denomination>', resource_class_kwargs={
api.add_resource(GetRate, '/api/get_rate/<denomination>', resource_class_kwargs={
"rates": rates})

if __name__ == '__main__':
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/usr/bin/env python3
2 changes: 2 additions & 0 deletions tests/samples.py
Original file line number Diff line number Diff line change
@@ -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}}

79 changes: 79 additions & 0 deletions tests/test_exchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 (
thb_rate_json,
)




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


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_get_thb(self):

# denomination = "thb"
url = f"/api/get_rate/THB"

thb_rate = {
"last": 20000,
"multiplier": 100,
"timestamp": 2000000
}

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 == thb_rate