Skip to content

Commit

Permalink
Refactor: Improve prices API code with better HTTP errors
Browse files Browse the repository at this point in the history
  • Loading branch information
hoh committed Jan 16, 2024
1 parent e6004cf commit 4616d76
Showing 1 changed file with 57 additions and 19 deletions.
76 changes: 57 additions & 19 deletions src/aleph/web/controllers/prices.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,85 @@
import logging
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional

from aiohttp import web
from dataclasses import dataclass
from aiohttp.web_exceptions import HTTPException
from aleph_message.models import ExecutableContent, ItemHash, MessageType
from dataclasses_json import DataClassJsonMixin

from aleph_message.models import ItemHash, MessageType
from aleph.db.accessors.messages import (
get_message_status, get_message_by_item_hash,
)
from aleph.db.accessors.messages import get_message_by_item_hash, get_message_status
from aleph.db.models import MessageDb, MessageStatusDb
from aleph.services.cost import compute_cost, compute_flow_cost
from aleph.types.db_session import DbSessionFactory
from aleph.types.db_session import DbSession, DbSessionFactory

LOGGER = logging.getLogger(__name__)


# This is not defined in aiohttp.web_exceptions
class HTTPProcessing(HTTPException):
status_code = 102


# Mapping between message statuses to their corresponding exceptions and messages
MESSAGE_STATUS_EXCEPTIONS = {
MessageStatusDb.statuses.PENDING: (HTTPProcessing, "Message still pending"),
MessageStatusDb.statuses.REJECTED: (web.HTTPNotFound, "This message was rejected"),
MessageStatusDb.statuses.FORGOTTEN: (
web.HTTPGone,
"This message has been forgotten",
),
}


@dataclass
class MessagePrice(DataClassJsonMixin):
"""Dataclass used to expose message required tokens."""

required_tokens: Optional[Decimal] = None


async def message_price(request: web.Request):
item_hash_str = request.match_info.get("item_hash")
async def get_executable_message(session: DbSession, item_hash_str: str) -> MessageDb:
"""Attempt to get an executable message from the database.
Raises an HTTP exception if the message is not found, not processed or is not an executable message.
"""

# Parse the item_hash_str into an ItemHash object
try:
item_hash = ItemHash(item_hash_str)
except ValueError:
raise web.HTTPUnprocessableEntity(body=f"Invalid message hash: {item_hash_str}")
raise web.HTTPBadRequest(body=f"Invalid message hash: {item_hash_str}")

# Get the message status from the database
message_status_db = get_message_status(session=session, item_hash=item_hash)
if not message_status_db:
raise web.HTTPNotFound()
# Loop through the status_exceptions to find a match and raise the corresponding exception
if message_status_db.status in MESSAGE_STATUS_EXCEPTIONS:
exception, message = MESSAGE_STATUS_EXCEPTIONS[message_status_db.status]
raise exception(body=f"{message}: {item_hash_str}")
assert message_status_db.status == MessageStatusDb.statuses.PROCESSED

# Get the message from the database
message = get_message_by_item_hash(session, item_hash)
if not message:
raise web.HTTPNotFound(body="Message not found, despite appearing as processed")
if message.type not in (MessageType.instance, MessageType.program):
raise web.HTTPBadRequest(
body=f"Message is not an executable message: {item_hash_str}"
)

return message


async def message_price(request: web.Request):
"""Returns the price of an executable message."""

session_factory: DbSessionFactory = request.app["session_factory"]
with session_factory() as session:
message_status_db = get_message_status(session=session, item_hash=item_hash)
if message_status_db is None:
raise web.HTTPNotFound()
message = get_message_by_item_hash(session, item_hash)

if not message or message.type != (MessageType.instance or MessageType.program):
raise web.HTTPUnprocessableEntity(
body=f"Invalid message hash: {item_hash_str}"
)
message = await get_executable_message(session, request.match_info["item_hash"])

content = message.parsed_content
content: ExecutableContent = message.parsed_content

if content.payment and content.payment.is_stream:
required_tokens = compute_flow_cost(session=session, content=content)
Expand Down

0 comments on commit 4616d76

Please sign in to comment.