Skip to content

Commit

Permalink
Show current ticket price on UpdateMessage (#33)
Browse files Browse the repository at this point in the history
* utils exceptions

* utils with dcrdata request wrapper

* ObserverMessage expire after 7 days

* TicketPrice created

* test TicketPrice

* UpdateMessage with ticket price

* minor fix
  • Loading branch information
rodrigondec authored and fguisso committed Dec 24, 2019
1 parent ed13959 commit ed4c21a
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 25 deletions.
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

0 comments on commit ed4c21a

Please sign in to comment.