Update deps. Fix transaction select. Move transaction randomizing logic to TransactionSerializer.

This commit is contained in:
Alejandro Cabeza Romero 2025-10-30 13:13:42 +01:00
parent 7f1a543681
commit a9950e3c44
No known key found for this signature in database
GPG Key ID: DA3D14AE478030FD
6 changed files with 45 additions and 46 deletions

View File

@ -3,14 +3,14 @@ name = "nomos-block-explorer"
version = "0.1.0" version = "0.1.0"
requires-python = ">=3.14,<3.15" requires-python = ">=3.14,<3.15"
dependencies = [ dependencies = [
"fastapi~=0.118.0", "fastapi~=0.120.2",
"httpx>=0.28.1", "httpx>=0.28.1",
"pydantic-settings>=2.11.0", "pydantic-settings>=2.11.0",
"python-on-whales~=0.78.0", "python-on-whales~=0.79.0",
"requests~=2.32.5", "requests~=2.32.5",
"rusty-results~=1.1.1", "rusty-results~=1.1.1",
"sqlmodel~=0.0.25", "sqlmodel~=0.0.25",
"uvicorn~=0.37.0", "uvicorn~=0.38.0",
] ]
[tool.pyright] [tool.pyright]

View File

@ -82,7 +82,7 @@ class TransactionRepository:
while True: while True:
statement = ( statement = (
select(Transaction, Block.slot, Block.id) select(Transaction)
.options(selectinload(Transaction.block)) .options(selectinload(Transaction.block))
.join(Block, Transaction.block_id == Block.id) .join(Block, Transaction.block_id == Block.id)
.where( .where(

View File

@ -1,9 +1,6 @@
import logging import logging
import os from typing import TYPE_CHECKING, List, Self
import random
from typing import TYPE_CHECKING, Any, List, Self
from pydantic.config import ExtraValues
from sqlalchemy import Column from sqlalchemy import Column
from sqlmodel import Field, Relationship from sqlmodel import Field, Relationship
@ -19,12 +16,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _should_randomize_transactions():
is_debug = os.getenv("DEBUG", "False").lower() == "true"
is_debug__randomize_transactions = os.getenv("DEBUG__RANDOMIZE_TRANSACTIONS", "False").lower() == "true"
return is_debug and is_debug__randomize_transactions
class Block(TimestampedModel, table=True): class Block(TimestampedModel, table=True):
__tablename__ = "block" __tablename__ = "block"
@ -54,25 +45,3 @@ class Block(TimestampedModel, table=True):
def with_transactions(self, transactions: List["Transaction"]) -> Self: def with_transactions(self, transactions: List["Transaction"]) -> Self:
self.transactions = transactions self.transactions = transactions
return self return self
@classmethod
def model_validate_json(
cls,
json_data: str | bytes | bytearray,
*,
strict: bool | None = None,
extra: ExtraValues | None = None,
context: Any | None = None,
by_alias: bool | None = None,
by_name: bool | None = None,
) -> Self:
self = super().model_validate_json(
json_data, strict=strict, extra=extra, context=context, by_alias=by_alias, by_name=by_name
)
if _should_randomize_transactions():
from models.transactions.transaction import Transaction
logger.debug("DEBUG and DEBUG__RANDOMIZE_TRANSACTIONS are enabled, randomizing Block's transactions.")
n_transactions = 0 if random.randint(0, 1) <= 0.3 else random.randint(1, 5)
self.transactions = [Transaction.from_random() for _ in range(n_transactions)]
return self

View File

@ -1,3 +1,5 @@
import logging
from os import getenv
from random import randint from random import randint
from typing import List, Self from typing import List, Self
@ -10,10 +12,32 @@ from node.api.serializers.signed_transaction import SignedTransactionSerializer
from utils.protocols import FromRandom from utils.protocols import FromRandom
def _should_randomize_transactions():
is_debug = getenv("DEBUG", "False").lower() == "true"
is_debug__randomize_transactions = getenv("DEBUG__RANDOMIZE_TRANSACTIONS", "False").lower() == "true"
return is_debug and is_debug__randomize_transactions
def _get_random_transactions() -> List[SignedTransactionSerializer]:
n = 1 if randint(0, 1) <= 0.5 else randint(2, 5)
return [SignedTransactionSerializer.from_random() for _ in range(n)]
logger = logging.getLogger(__name__)
class BlockSerializer(NbeSerializer, FromRandom): class BlockSerializer(NbeSerializer, FromRandom):
header: HeaderSerializer header: HeaderSerializer
transactions: List[SignedTransactionSerializer] transactions: List[SignedTransactionSerializer]
@classmethod
def model_validate_json(cls, *args, **kwargs) -> Self:
self = super().model_validate_json(*args, **kwargs)
if _should_randomize_transactions():
logger.debug("DEBUG and DEBUG__RANDOMIZE_TRANSACTIONS are enabled, randomizing Block's transactions.")
self.transactions = _get_random_transactions()
return self
def into_block(self) -> Block: def into_block(self) -> Block:
transactions = [transaction.into_transaction() for transaction in self.transactions] transactions = [transaction.into_transaction() for transaction in self.transactions]
return Block.model_validate( return Block.model_validate(
@ -29,6 +53,11 @@ class BlockSerializer(NbeSerializer, FromRandom):
@classmethod @classmethod
def from_random(cls, *, slot: Option[int] = None) -> Self: def from_random(cls, *, slot: Option[int] = None) -> Self:
slot = slot or Empty() slot = slot or Empty()
n = 1 if randint(0, 1) <= 0.5 else randint(2, 5) transactions = _get_random_transactions()
transactions = [SignedTransactionSerializer.from_random() for _ in range(n)]
return cls.model_validate({"header": HeaderSerializer.from_random(slot=slot), "transactions": transactions}) return cls.model_validate({"header": HeaderSerializer.from_random(slot=slot), "transactions": transactions})
def __str__(self) -> str:
return f"BlockSerializer(slot={self.header.slot})"
def __repr__(self) -> str:
return f"<BlockSerializer(slot={self.header.slot}, hash={self.header.hash.hex()})>"

View File

@ -9,7 +9,6 @@ from db.blocks import BlockRepository
from db.clients import SqliteClient from db.clients import SqliteClient
from db.transaction import TransactionRepository from db.transaction import TransactionRepository
from models.block import Block from models.block import Block
from models.transactions.transaction import Transaction
from node.api.fake import FakeNodeApi from node.api.fake import FakeNodeApi
from node.api.http import HttpNodeApi from node.api.http import HttpNodeApi
from node.api.serializers.block import BlockSerializer from node.api.serializers.block import BlockSerializer
@ -28,8 +27,8 @@ async def node_lifespan(app: "NBE") -> AsyncGenerator[None]:
app.state.node_manager = FakeNodeManager() app.state.node_manager = FakeNodeManager()
# app.state.node_manager = DockerModeManager(app.settings.node_compose_filepath) # app.state.node_manager = DockerModeManager(app.settings.node_compose_filepath)
app.state.node_api = FakeNodeApi() # app.state.node_api = FakeNodeApi()
# app.state.node_api = HttpNodeApi(host="127.0.0.1", port=18080) app.state.node_api = HttpNodeApi(host="127.0.0.1", port=18080)
app.state.db_client = db_client app.state.db_client = db_client
app.state.block_repository = BlockRepository(db_client) app.state.block_repository = BlockRepository(db_client)

View File

@ -1,4 +1,4 @@
from logging import error, warn import logging
from python_on_whales import DockerException from python_on_whales import DockerException
from python_on_whales.docker_client import DockerClient from python_on_whales.docker_client import DockerClient
@ -6,6 +6,8 @@ from rusty_results import Err, Ok, Result
from node.manager.base import NodeManager from node.manager.base import NodeManager
logger = logging.getLogger(__name__)
class DockerModeManager(NodeManager): class DockerModeManager(NodeManager):
def __init__(self, compose_filepath: str): def __init__(self, compose_filepath: str):
@ -16,10 +18,10 @@ class DockerModeManager(NodeManager):
match self.ps(): match self.ps():
case Err(1): case Err(1):
error("Compose services are not running.") logger.error("Docker compose services are not running.")
exit(21) # FIXME: There's too much output here. exit(21) # FIXME: There's too much output here: Don't exit here, raise error and exit outside.
case Err(_): case Err(_):
error("Failed to run docker compose.") logger.error("Failed to run docker compose.")
exit(20) exit(20)
def ps(self, only_running: bool = True) -> Result: def ps(self, only_running: bool = True) -> Result:
@ -32,7 +34,7 @@ class DockerModeManager(NodeManager):
async def start(self): async def start(self):
services = self.ps().map(lambda _services: len(_services)).expect("Failed to get compose services.") services = self.ps().map(lambda _services: len(_services)).expect("Failed to get compose services.")
if services > 0: if services > 0:
warn("Compose services are already running.") logger.warn("Compose services are already running.")
return return
self.client.compose.up( self.client.compose.up(