Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show current ticket price on UpdateMessage #33

Merged
merged 8 commits into from
Dec 24, 2019
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
4 changes: 2 additions & 2 deletions bot/commands/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from bot.core import BotTelegramCore
from db.subject import Subject
from db.observer import Observer
from db.exceptions import (ObserverAlreadyRegisteredError,
ObserverNotRegisteredError)
from utils.exceptions import (ObserverAlreadyRegisteredError,
ObserverNotRegisteredError)


logging.basicConfig(
Expand Down
4 changes: 2 additions & 2 deletions bot/commands/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from telegram.ext import CommandHandler, CallbackContext

from bot.core import BotTelegramCore
from bot.utils import convert_dcr
from bot.exceptions import DcrDataAPIError
from utils.utils import convert_dcr
from utils.exceptions import DcrDataAPIError


logging.basicConfig(
Expand Down
2 changes: 1 addition & 1 deletion bot/commands/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from telegram.ext import CommandHandler, CallbackContext

from bot.core import BotTelegramCore
from bot.utils import build_menu
from utils.utils import build_menu
from db.subject import Subject
from db.observer import UserObserver

Expand Down
3 changes: 0 additions & 3 deletions bot/exceptions.py

This file was deleted.

2 changes: 1 addition & 1 deletion db/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ObserverMessage(Document):
meta = {
'ordering': ['datetime'],
'indexes': [
{'fields': ['datetime'], 'expireAfterSeconds': 1*24*60*60}
{'fields': ['datetime'], 'expireAfterSeconds': 7*24*60*60}
]
}

Expand Down
4 changes: 2 additions & 2 deletions db/subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
StringField, ListField,
ReferenceField, NULLIFY)

from db.exceptions import (ObserverNotRegisteredError,
ObserverAlreadyRegisteredError)
from utils.exceptions import (ObserverNotRegisteredError,
ObserverAlreadyRegisteredError)
from db.observer import Observer, UserObserver


Expand Down
62 changes: 62 additions & 0 deletions db/ticket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging

import pendulum
from mongoengine import (
Document,
FloatField, DateTimeField)

from utils.dcrdata import request_dcr_data


logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)

logger = logging.getLogger(__name__)


class TicketPrice(Document):
endpoint = "stake/diff"

price = FloatField(required=True)
datetime = DateTimeField(default=pendulum.now, required=True)

meta = {
'ordering': ['datetime'],
'indexes': [
{'fields': ['datetime'], 'expireAfterSeconds': 7*24*60*60}
]
}

def __str__(self):
return f"{self.price:.2f} DCR"

@property
def pendulum_datetime(self):
return pendulum.instance(self.datetime).in_tz('America/Sao_Paulo')

def is_past_expire(self):
now = pendulum.now()
last = self.pendulum_datetime
diff = now - last
return diff.in_seconds() >= 2*60*60

@classmethod
def _fetch_new_ticket_price(cls):
price = request_dcr_data(cls.endpoint)
price = price.get('current')
return cls(price)

@classmethod
def get_last(cls):
last_ticket_price = cls.objects.order_by('-datetime').first()

try:
if last_ticket_price.is_past_expire():
last_ticket_price = cls._fetch_new_ticket_price()
except AttributeError:
last_ticket_price = cls._fetch_new_ticket_price()
finally:
last_ticket_price.save()

return last_ticket_price
6 changes: 4 additions & 2 deletions db/update_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
EmbeddedDocumentListField, ReferenceField)

from db.subject import Subject
from db.ticket import TicketPrice


class Amount(EmbeddedDocument):
Expand Down Expand Up @@ -56,8 +57,9 @@ class UpdateMessage(Document):
datetime = DateTimeField(default=pendulum.now, required=True)

def __str__(self):
string = f"<b>{self.subject.header}</b>\n"
string += f"<i>default session: {self.subject.default_session}</i>\n\n"
string = f"<b>{self.subject.header}</b>\n\n"
string += f"<i>Default session: {self.subject.default_session}</i>\n\n"
string += f"Ticket price: {TicketPrice.get_last()}\n\n"
for index, msg in enumerate(self.sessions):
string += f"<code>{msg}</code>"
string += "\n\n" if index != len(self.sessions) - 1 else ""
Expand Down
87 changes: 87 additions & 0 deletions tests/db/test_ticket_price.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from unittest import TestCase, mock

import pendulum
import pytest

from tests.fixtures import mongo # noqa F401
from db.ticket import TicketPrice


@pytest.mark.usefixtures('mongo')
class TicketPriceTestCase(TestCase):
def test_create(self):
self.assertEqual(TicketPrice.objects.count(), 0)

instance = TicketPrice(150.5).save()
self.assertEqual(TicketPrice.objects.count(), 1)

self.assertEqual(instance.price, 150.5)
self.assertTrue(instance.datetime)

def test_is_past_expire(self):
self.assertEqual(TicketPrice.objects.count(), 0)
TicketPrice(150.5).save()
self.assertEqual(TicketPrice.objects.count(), 1)

instance = TicketPrice.objects.first()

self.assertFalse(instance.is_past_expire())

instance.datetime = pendulum.yesterday()
self.assertTrue(instance.is_past_expire())

@mock.patch('db.ticket.request_dcr_data')
def test_fetch_new_ticket_price(self, mocked_request_dcr_data):
self.assertIsInstance(mocked_request_dcr_data, mock.MagicMock)
mocked_request_dcr_data.return_value = mock.MagicMock(
get=mock.MagicMock(
return_value=150.5
)
)

instance = TicketPrice._fetch_new_ticket_price()
self.assertEqual(instance.price, 150.5)
self.assertTrue(instance.datetime)

@mock.patch('db.ticket.request_dcr_data')
def test_get_last_price_exception(self, mocked_request_dcr_data):
self.assertIsInstance(mocked_request_dcr_data, mock.MagicMock)
mocked_request_dcr_data.return_value = mock.MagicMock(
get=mock.MagicMock(
return_value=150.5
)
)

self.assertEqual(TicketPrice.objects.count(), 0)

instance = TicketPrice.get_last()
self.assertEqual(TicketPrice.objects.count(), 1)
self.assertEqual(instance.price, 150.5)
self.assertTrue(instance.datetime)

def test_get_last_price_existing(self):
TicketPrice(130).save()

self.assertEqual(TicketPrice.objects.count(), 1)

instance = TicketPrice.get_last()
self.assertEqual(TicketPrice.objects.count(), 1)
self.assertEqual(instance.price, 130)
self.assertTrue(instance.datetime)

@mock.patch('db.ticket.request_dcr_data')
def test_get_last_price_existing_expired(self, mocked_request_dcr_data):
self.assertIsInstance(mocked_request_dcr_data, mock.MagicMock)
mocked_request_dcr_data.return_value = mock.MagicMock(
get=mock.MagicMock(
return_value=150.5
)
)

TicketPrice(130, pendulum.yesterday()).save()
self.assertEqual(TicketPrice.objects.count(), 1)

instance = TicketPrice.get_last()
self.assertEqual(TicketPrice.objects.count(), 2)
self.assertEqual(instance.price, 150.5)
self.assertTrue(instance.datetime)
Empty file added utils/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions utils/dcrdata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import json

import requests

from utils.exceptions import DcrDataAPIError


DCRDATA_API_URL = "https://dcrdata.decred.org/api"


def request_dcr_data(endpoint):
dcrdata_response = requests.get(f"{DCRDATA_API_URL}/{endpoint}")
if dcrdata_response.status_code != 200:
raise DcrDataAPIError(dcrdata_response.content)

return json.loads(dcrdata_response.content)
3 changes: 3 additions & 0 deletions db/exceptions.py → utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

class DcrDataAPIError(Exception):
pass


class ObserverNotRegisteredError(Exception):
pass
Expand Down
16 changes: 4 additions & 12 deletions bot/utils.py → utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import json

import requests

from bot.exceptions import DcrDataAPIError
from utils.dcrdata import request_dcr_data


def build_menu(buttons,
Expand All @@ -18,13 +14,9 @@ def build_menu(buttons,


def convert_dcr(dcr_amount: float, target_currency: str):
dcrdata_response = requests.get(f"https://dcrdata.decred.org/api/exchanges?"
f"code={target_currency}")
if dcrdata_response.status_code != 200:
raise DcrDataAPIError(dcrdata_response.content)

dcr_to_usd_value = json.loads(dcrdata_response.content)
endpoint = "exchanges"
dcr_to_usd_value = request_dcr_data(endpoint)
dcr_to_usd_value = dcr_to_usd_value.get("price")

if target_currency == 'USD':
return dcr_amount*dcr_to_usd_value
return dcr_amount * dcr_to_usd_value