chore: add uv, linting, fixes (#39)
Some checks failed
CI / lint (push) Has been cancelled
/ release (push) Has been cancelled
CI / tests (push) Has been cancelled
/ pullrequest (push) Has been cancelled

* chore: add uv, linting, fixes
This commit is contained in:
dni ⚡ 2025-10-30 10:43:27 +01:00 committed by GitHub
parent 15079c3e58
commit 35584a230f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 2405 additions and 2752 deletions

View file

@ -11,14 +11,9 @@ jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint] needs: [lint]
strategy:
matrix:
python-version: ['3.9', '3.10']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: lnbits/lnbits/.github/actions/prepare@dev - uses: lnbits/lnbits/.github/actions/prepare@dev
with:
python-version: ${{ matrix.python-version }}
- name: Run pytest - name: Run pytest
uses: pavelzw/pytest-action@v2 uses: pavelzw/pytest-action@v2
env: env:
@ -30,5 +25,5 @@ jobs:
job-summary: true job-summary: true
emoji: false emoji: false
click-to-expand: true click-to-expand: true
custom-pytest: poetry run pytest custom-pytest: uv run pytest
report-title: 'test (${{ matrix.python-version }})' report-title: 'test'

View file

@ -5,27 +5,27 @@ format: prettier black ruff
check: mypy pyright checkblack checkruff checkprettier check: mypy pyright checkblack checkruff checkprettier
prettier: prettier:
poetry run ./node_modules/.bin/prettier --write . uv run ./node_modules/.bin/prettier --write .
pyright: pyright:
poetry run ./node_modules/.bin/pyright uv run ./node_modules/.bin/pyright
mypy: mypy:
poetry run mypy . uv run mypy .
black: black:
poetry run black . uv run black .
ruff: ruff:
poetry run ruff check . --fix uv run ruff check . --fix
checkruff: checkruff:
poetry run ruff check . uv run ruff check .
checkprettier: checkprettier:
poetry run ./node_modules/.bin/prettier --check . uv run ./node_modules/.bin/prettier --check .
checkblack: checkblack:
poetry run black --check . uv run black --check .
checkeditorconfig: checkeditorconfig:
editorconfig-checker editorconfig-checker
@ -34,15 +34,15 @@ test:
LNBITS_DATA_FOLDER="./tests/data" \ LNBITS_DATA_FOLDER="./tests/data" \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
DEBUG=true \ DEBUG=true \
poetry run pytest uv run pytest
install-pre-commit-hook: install-pre-commit-hook:
@echo "Installing pre-commit hook to git" @echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall" @echo "Uninstall the hook with uv run pre-commit uninstall"
poetry run pre-commit install uv run pre-commit install
pre-commit: pre-commit:
poetry run pre-commit run --all-files uv run pre-commit run --all-files
checkbundle: checkbundle:

11
crud.py
View file

@ -1,5 +1,4 @@
import json import json
from typing import Optional
from lnbits.db import Database from lnbits.db import Database
@ -21,7 +20,7 @@ async def update_relay(relay: NostrRelay) -> NostrRelay:
return relay return relay
async def get_relay(user_id: str, relay_id: str) -> Optional[NostrRelay]: async def get_relay(user_id: str, relay_id: str) -> NostrRelay | None:
return await db.fetchone( return await db.fetchone(
"SELECT * FROM nostrrelay.relays WHERE user_id = :user_id AND id = :id", "SELECT * FROM nostrrelay.relays WHERE user_id = :user_id AND id = :id",
{"user_id": user_id, "id": relay_id}, {"user_id": user_id, "id": relay_id},
@ -29,7 +28,7 @@ async def get_relay(user_id: str, relay_id: str) -> Optional[NostrRelay]:
) )
async def get_relay_by_id(relay_id: str) -> Optional[NostrRelay]: async def get_relay_by_id(relay_id: str) -> NostrRelay | None:
"""Note: it does not require `user_id`. Can read any relay. Use it with care.""" """Note: it does not require `user_id`. Can read any relay. Use it with care."""
return await db.fetchone( return await db.fetchone(
"SELECT * FROM nostrrelay.relays WHERE id = :id", "SELECT * FROM nostrrelay.relays WHERE id = :id",
@ -58,7 +57,7 @@ async def get_config_for_all_active_relays() -> dict:
return active_relay_configs return active_relay_configs
async def get_public_relay(relay_id: str) -> Optional[dict]: async def get_public_relay(relay_id: str) -> dict | None:
relay = await db.fetchone( relay = await db.fetchone(
"SELECT * FROM nostrrelay.relays WHERE id = :id", "SELECT * FROM nostrrelay.relays WHERE id = :id",
{"id": relay_id}, {"id": relay_id},
@ -130,7 +129,7 @@ async def get_events(
return events return events
async def get_event(relay_id: str, event_id: str) -> Optional[NostrEvent]: async def get_event(relay_id: str, event_id: str) -> NostrEvent | None:
event = await db.fetchone( event = await db.fetchone(
"SELECT * FROM nostrrelay.events WHERE relay_id = :relay_id AND id = :id", "SELECT * FROM nostrrelay.events WHERE relay_id = :relay_id AND id = :id",
{"relay_id": relay_id, "id": event_id}, {"relay_id": relay_id, "id": event_id},
@ -286,7 +285,7 @@ async def delete_account(relay_id: str, pubkey: str):
async def get_account( async def get_account(
relay_id: str, relay_id: str,
pubkey: str, pubkey: str,
) -> Optional[NostrAccount]: ) -> NostrAccount | None:
return await db.fetchone( return await db.fetchone(
""" """
SELECT * FROM nostrrelay.accounts SELECT * FROM nostrrelay.accounts

View file

@ -1,5 +1,3 @@
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -16,8 +14,8 @@ class BuyOrder(BaseModel):
class NostrPartialAccount(BaseModel): class NostrPartialAccount(BaseModel):
relay_id: str relay_id: str
pubkey: str pubkey: str
allowed: Optional[bool] = None allowed: bool | None = None
blocked: Optional[bool] = None blocked: bool | None = None
class NostrAccount(BaseModel): class NostrAccount(BaseModel):
@ -44,4 +42,4 @@ class NostrEventTags(BaseModel):
event_id: str event_id: str
name: str name: str
value: str value: str
extra: Optional[str] = None extra: str | None = None

2629
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,37 +1,37 @@
[tool.poetry] [project]
name = "nostrrelay" name = "nostrrelay"
version = "0.0.0" version = "0.0.0"
description = "nostrrelay" requires-python = ">=3.10,<3.13"
authors = ["dni <dni@lnbits.com>"] description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
urls = { Homepage = "https://lnbits.com", Repository = "https://github.com/lnbits/nostrrelay" }
dependencies = [ "lnbits>1" ]
[tool.poetry.dependencies] [tool.poetry]
python = "^3.10 | ^3.9" package-mode = false
lnbits = {allow-prereleases = true, version = "*"}
[tool.poetry.group.dev.dependencies] [dependency-groups]
black = "^24.3.0" dev= [
pytest-asyncio = "^0.21.0" "black",
pytest = "^7.3.2" "pytest-asyncio",
mypy = "^1.5.1" "pytest",
pre-commit = "^3.2.2" "mypy==1.17.1",
ruff = "^0.3.2" "pre-commit",
pytest-md = "^0.2.0" "ruff",
"pytest-md",
[build-system] ]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.mypy] [tool.mypy]
exclude = [ plugins = ["pydantic.mypy"]
"boltz_client"
] [tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = [ module = [
"lnbits.*",
"loguru.*",
"fastapi.*",
"pydantic.*",
"embit.*",
"secp256k1.*", "secp256k1.*",
] ]
ignore_missing_imports = "True" ignore_missing_imports = "True"
@ -86,8 +86,8 @@ classmethod-decorators = [
# [tool.ruff.lint.extend-per-file-ignores] # [tool.ruff.lint.extend-per-file-ignores]
# "views_api.py" = ["F401"] # "views_api.py" = ["F401"]
# [tool.ruff.lint.mccabe] [tool.ruff.lint.mccabe]
# max-complexity = 10 max-complexity = 11
[tool.ruff.lint.flake8-bugbear] [tool.ruff.lint.flake8-bugbear]
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.

View file

@ -1,6 +1,7 @@
import json import json
import time import time
from typing import Any, Awaitable, Callable, List, Optional from collections.abc import Awaitable, Callable
from typing import Any
from fastapi import WebSocket from fastapi import WebSocket
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
@ -25,17 +26,17 @@ class NostrClientConnection:
def __init__(self, relay_id: str, websocket: WebSocket): def __init__(self, relay_id: str, websocket: WebSocket):
self.websocket = websocket self.websocket = websocket
self.relay_id = relay_id self.relay_id = relay_id
self.filters: List[NostrFilter] = [] self.filters: list[NostrFilter] = []
self.auth_pubkey: Optional[str] = None # set if authenticated self.auth_pubkey: str | None = None # set if authenticated
self._auth_challenge: Optional[str] = None self._auth_challenge: str | None = None
self._auth_challenge_created_at = 0 self._auth_challenge_created_at = 0
self.event_validator = EventValidator(self.relay_id) self.event_validator = EventValidator(self.relay_id)
self.broadcast_event: Optional[ self.broadcast_event: (
Callable[[NostrClientConnection, NostrEvent], Awaitable[None]] Callable[[NostrClientConnection, NostrEvent], Awaitable[None]] | None
] = None ) = None
self.get_client_config: Optional[Callable[[], RelaySpec]] = None self.get_client_config: Callable[[], RelaySpec] | None = None
async def start(self): async def start(self):
await self.websocket.accept() await self.websocket.accept()
@ -50,7 +51,7 @@ class NostrClientConnection:
except Exception as e: except Exception as e:
logger.warning(e) logger.warning(e)
async def stop(self, reason: Optional[str]): async def stop(self, reason: str | None):
message = reason if reason else "Server closed webocket" message = reason if reason else "Server closed webocket"
try: try:
await self._send_msg(["NOTICE", message]) await self._send_msg(["NOTICE", message])
@ -98,7 +99,7 @@ class NostrClientConnection:
if self.broadcast_event: if self.broadcast_event:
await self.broadcast_event(self, e) await self.broadcast_event(self, e)
async def _handle_message(self, data: List) -> List: async def _handle_message(self, data: list) -> list:
if len(data) < 2: if len(data) < 2:
return [] return []
@ -121,7 +122,9 @@ class NostrClientConnection:
# Handle multiple filters in REQ message # Handle multiple filters in REQ message
responses = [] responses = []
for filter_data in data[2:]: for filter_data in data[2:]:
response = await self._handle_request(subscription_id, NostrFilter.parse_obj(filter_data)) response = await self._handle_request(
subscription_id, NostrFilter.parse_obj(filter_data)
)
responses.extend(response) responses.extend(response)
return responses return responses
if message_type == NostrEventType.CLOSE: if message_type == NostrEventType.CLOSE:
@ -133,7 +136,7 @@ class NostrClientConnection:
async def _handle_event(self, e: NostrEvent): async def _handle_event(self, e: NostrEvent):
logger.info(f"nostr event: [{e.kind}, {e.pubkey}, '{e.content}']") logger.info(f"nostr event: [{e.kind}, {e.pubkey}, '{e.content}']")
resp_nip20: List[Any] = ["OK", e.id] resp_nip20: list[Any] = ["OK", e.id]
if e.is_auth_response_event: if e.is_auth_response_event:
valid, message = self.event_validator.validate_auth_event( valid, message = self.event_validator.validate_auth_event(
@ -172,12 +175,12 @@ class NostrClientConnection:
if d_tag_value: if d_tag_value:
deletion_filter = NostrFilter( deletion_filter = NostrFilter(
kinds=[e.kind], kinds=[e.kind],
authors=[e.pubkey], authors=[e.pubkey],
**{"#d": [d_tag_value]}, **{"#d": [d_tag_value]}, # type: ignore
until=e.created_at until=e.created_at,
) )
await delete_events(self.relay_id, deletion_filter) await delete_events(self.relay_id, deletion_filter)
if not e.is_ephemeral_event: if not e.is_ephemeral_event:
await create_event(e) await create_event(e)
@ -201,7 +204,7 @@ class NostrClientConnection:
raise Exception("Client not ready!") raise Exception("Client not ready!")
return self.get_client_config() return self.get_client_config()
async def _send_msg(self, data: List): async def _send_msg(self, data: list):
await self.websocket.send_text(json.dumps(data)) await self.websocket.send_text(json.dumps(data))
async def _handle_delete_event(self, event: NostrEvent): async def _handle_delete_event(self, event: NostrEvent):
@ -214,7 +217,7 @@ class NostrClientConnection:
async def _handle_request( async def _handle_request(
self, subscription_id: str, nostr_filter: NostrFilter self, subscription_id: str, nostr_filter: NostrFilter
) -> List: ) -> list:
if self.config.require_auth_filter: if self.config.require_auth_filter:
if not self.auth_pubkey: if not self.auth_pubkey:
return [["AUTH", self._current_auth_challenge()]] return [["AUTH", self._current_auth_challenge()]]

View file

@ -1,5 +1,3 @@
from typing import List
from ..crud import get_config_for_all_active_relays from ..crud import get_config_for_all_active_relays
from .client_connection import NostrClientConnection from .client_connection import NostrClientConnection
from .event import NostrEvent from .event import NostrEvent
@ -47,7 +45,7 @@ class NostrClientManager:
def get_relay_config(self, relay_id: str) -> RelaySpec: def get_relay_config(self, relay_id: str) -> RelaySpec:
return self._active_relays[relay_id] return self._active_relays[relay_id]
def clients(self, relay_id: str) -> List[NostrClientConnection]: def clients(self, relay_id: str) -> list[NostrClientConnection]:
if relay_id not in self._clients: if relay_id not in self._clients:
self._clients[relay_id] = [] self._clients[relay_id] = []
return self._clients[relay_id] return self._clients[relay_id]

View file

@ -1,5 +1,5 @@
import time import time
from typing import Callable, Optional, Tuple from collections.abc import Callable
from ..crud import get_account, get_storage_for_public_key, prune_old_events from ..crud import get_account, get_storage_for_public_key, prune_old_events
from ..helpers import extract_domain from ..helpers import extract_domain
@ -15,11 +15,11 @@ class EventValidator:
self._last_event_timestamp = 0 # in hours self._last_event_timestamp = 0 # in hours
self._event_count_per_timestamp = 0 self._event_count_per_timestamp = 0
self.get_client_config: Optional[Callable[[], RelaySpec]] = None self.get_client_config: Callable[[], RelaySpec] | None = None
async def validate_write( async def validate_write(
self, e: NostrEvent, publisher_pubkey: str self, e: NostrEvent, publisher_pubkey: str
) -> Tuple[bool, str]: ) -> tuple[bool, str]:
valid, message = self._validate_event(e) valid, message = self._validate_event(e)
if not valid: if not valid:
return (valid, message) return (valid, message)
@ -34,8 +34,8 @@ class EventValidator:
return True, "" return True, ""
def validate_auth_event( def validate_auth_event(
self, e: NostrEvent, auth_challenge: Optional[str] self, e: NostrEvent, auth_challenge: str | None
) -> Tuple[bool, str]: ) -> tuple[bool, str]:
valid, message = self._validate_event(e) valid, message = self._validate_event(e)
if not valid: if not valid:
return (valid, message) return (valid, message)
@ -59,7 +59,7 @@ class EventValidator:
raise Exception("EventValidator not ready!") raise Exception("EventValidator not ready!")
return self.get_client_config() return self.get_client_config()
def _validate_event(self, e: NostrEvent) -> Tuple[bool, str]: def _validate_event(self, e: NostrEvent) -> tuple[bool, str]:
if self._exceeded_max_events_per_hour(): if self._exceeded_max_events_per_hour():
return False, "Exceeded max events per hour limit'!" return False, "Exceeded max events per hour limit'!"
@ -76,7 +76,7 @@ class EventValidator:
async def _validate_storage( async def _validate_storage(
self, pubkey: str, event_size_bytes: int self, pubkey: str, event_size_bytes: int
) -> Tuple[bool, str]: ) -> tuple[bool, str]:
if self.config.is_read_only_relay: if self.config.is_read_only_relay:
return False, "Cannot write event, relay is read-only" return False, "Cannot write event, relay is read-only"
@ -124,7 +124,7 @@ class EventValidator:
return self._event_count_per_timestamp > self.config.max_events_per_hour return self._event_count_per_timestamp > self.config.max_events_per_hour
def _created_at_in_range(self, created_at: int) -> Tuple[bool, str]: def _created_at_in_range(self, created_at: int) -> tuple[bool, str]:
current_time = round(time.time()) current_time = round(time.time())
if self.config.created_at_in_past != 0: if self.config.created_at_in_past != 0:
if created_at < (current_time - self.config.created_at_in_past): if created_at < (current_time - self.config.created_at_in_past):

View file

@ -1,5 +1,3 @@
from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from .event import NostrEvent from .event import NostrEvent
@ -12,10 +10,10 @@ class NostrFilter(BaseModel):
ids: list[str] = [] ids: list[str] = []
authors: list[str] = [] authors: list[str] = []
kinds: list[int] = [] kinds: list[int] = []
subscription_id: Optional[str] = None subscription_id: str | None = None
since: Optional[int] = None since: int | None = None
until: Optional[int] = None until: int | None = None
limit: Optional[int] = None limit: int | None = None
def matches(self, e: NostrEvent) -> bool: def matches(self, e: NostrEvent) -> bool:
# todo: starts with # todo: starts with
@ -93,9 +91,12 @@ class NostrFilter(BaseModel):
if len(self.d): if len(self.d):
d_s = ",".join([f"'{d}'" for d in self.d]) d_s = ",".join([f"'{d}'" for d in self.d])
d_join = "INNER JOIN nostrrelay.event_tags d_tags ON nostrrelay.events.id = d_tags.event_id" d_join = (
"INNER JOIN nostrrelay.event_tags d_tags "
"ON nostrrelay.events.id = d_tags.event_id"
)
d_where = f" d_tags.value in ({d_s}) AND d_tags.name = 'd'" d_where = f" d_tags.value in ({d_s}) AND d_tags.name = 'd'"
inner_joins.append(d_join) inner_joins.append(d_join)
where.append(d_where) where.append(d_where)

View file

@ -1,5 +1,3 @@
from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@ -100,11 +98,11 @@ class RelaySpec(RelayPublicSpec, WalletSpec, AuthSpec):
class NostrRelay(BaseModel): class NostrRelay(BaseModel):
id: str id: str
user_id: Optional[str] = None user_id: str | None = None
name: str name: str
description: Optional[str] = None description: str | None = None
pubkey: Optional[str] = None pubkey: str | None = None
contact: Optional[str] = None contact: str | None = None
active: bool = False active: bool = False
meta: RelaySpec = RelaySpec() meta: RelaySpec = RelaySpec()

View file

@ -1,7 +1,7 @@
import asyncio import asyncio
import inspect import inspect
from typing import List, Optional
import pytest
import pytest_asyncio import pytest_asyncio
from lnbits.db import Database from lnbits.db import Database
from loguru import logger from loguru import logger
@ -14,11 +14,11 @@ from .helpers import get_fixtures
class EventFixture(BaseModel): class EventFixture(BaseModel):
name: str name: str
exception: Optional[str] exception: str | None
data: NostrEvent data: NostrEvent
@pytest_asyncio.fixture(scope="session") @pytest.fixture(scope="session")
def event_loop(): def event_loop():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
yield loop yield loop
@ -33,16 +33,16 @@ async def migrate_db():
print("### 1000") print("### 1000")
logger.info(f"Running migration '{key}'.") logger.info(f"Running migration '{key}'.")
await migrate(db) await migrate(db)
return migrations return db
@pytest_asyncio.fixture(scope="session") @pytest.fixture(scope="session")
def valid_events(migrate_db) -> List[EventFixture]: def valid_events(migrate_db) -> list[EventFixture]:
data = get_fixtures("events") data = get_fixtures("events")
return [EventFixture.parse_obj(e) for e in data["valid"]] return [EventFixture.parse_obj(e) for e in data["valid"]]
@pytest_asyncio.fixture(scope="session") @pytest.fixture(scope="session")
def invalid_events(migrate_db) -> List[EventFixture]: def invalid_events(migrate_db) -> list[EventFixture]:
data = get_fixtures("events") data = get_fixtures("events")
return [EventFixture.parse_obj(e) for e in data["invalid"]] return [EventFixture.parse_obj(e) for e in data["invalid"]]

View file

@ -1,6 +1,5 @@
import asyncio import asyncio
from json import dumps, loads from json import dumps, loads
from typing import Optional
import pytest import pytest
from fastapi import WebSocket from fastapi import WebSocket
@ -41,7 +40,7 @@ class MockWebSocket(WebSocket):
async def wire_mock_data(self, data: dict): async def wire_mock_data(self, data: dict):
await self.fake_wire.put(dumps(data)) await self.fake_wire.put(dumps(data))
async def close(self, code: int = 1000, reason: Optional[str] = None) -> None: async def close(self, code: int = 1000, reason: str | None = None) -> None:
logger.info(f"{code}: {reason}") logger.info(f"{code}: {reason}")

View file

@ -1,5 +1,4 @@
import json import json
from typing import List
import pytest import pytest
from loguru import logger from loguru import logger
@ -16,7 +15,7 @@ from .conftest import EventFixture
RELAY_ID = "r1" RELAY_ID = "r1"
def test_valid_event_id_and_signature(valid_events: List[EventFixture]): def test_valid_event_id_and_signature(valid_events: list[EventFixture]):
for f in valid_events: for f in valid_events:
try: try:
f.data.check_signature() f.data.check_signature()
@ -25,14 +24,14 @@ def test_valid_event_id_and_signature(valid_events: List[EventFixture]):
raise e raise e
def test_invalid_event_id_and_signature(invalid_events: List[EventFixture]): def test_invalid_event_id_and_signature(invalid_events: list[EventFixture]):
for f in invalid_events: for f in invalid_events:
with pytest.raises(ValueError, match=f.exception): with pytest.raises(ValueError, match=f.exception):
f.data.check_signature() f.data.check_signature()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_valid_event_crud(valid_events: List[EventFixture]): async def test_valid_event_crud(valid_events: list[EventFixture]):
author = "a24496bca5dd73300f4e5d5d346c73132b7354c597fcbb6509891747b4689211" author = "a24496bca5dd73300f4e5d5d346c73132b7354c597fcbb6509891747b4689211"
event_id = "3219eec7427e365585d5adf26f5d2dd2709d3f0f2c0e1f79dc9021e951c67d96" event_id = "3219eec7427e365585d5adf26f5d2dd2709d3f0f2c0e1f79dc9021e951c67d96"
reply_event_id = "6b2b6cb9c72caaf3dfbc5baa5e68d75ac62f38ec011b36cc83832218c36e4894" reply_event_id = "6b2b6cb9c72caaf3dfbc5baa5e68d75ac62f38ec011b36cc83832218c36e4894"
@ -65,7 +64,7 @@ async def get_by_id(data: NostrEvent, test_name: str):
), f"Restored event is different for fixture '{test_name}'" ), f"Restored event is different for fixture '{test_name}'"
async def filter_by_id(all_events: List[NostrEvent], data: NostrEvent, test_name: str): async def filter_by_id(all_events: list[NostrEvent], data: NostrEvent, test_name: str):
nostr_filter = NostrFilter(ids=[data.id]) nostr_filter = NostrFilter(ids=[data.id])
events = await get_events(RELAY_ID, nostr_filter) events = await get_events(RELAY_ID, nostr_filter)
@ -81,7 +80,7 @@ async def filter_by_id(all_events: List[NostrEvent], data: NostrEvent, test_name
), f"Filtered event is different for fixture '{test_name}'" ), f"Filtered event is different for fixture '{test_name}'"
async def filter_by_author(all_events: List[NostrEvent], author): async def filter_by_author(all_events: list[NostrEvent], author):
nostr_filter = NostrFilter(authors=[author]) nostr_filter = NostrFilter(authors=[author])
events_by_author = await get_events(RELAY_ID, nostr_filter) events_by_author = await get_events(RELAY_ID, nostr_filter)
assert len(events_by_author) == 5, "Failed to query by authors" assert len(events_by_author) == 5, "Failed to query by authors"
@ -90,7 +89,7 @@ async def filter_by_author(all_events: List[NostrEvent], author):
assert len(filtered_events) == 5, "Failed to filter by authors" assert len(filtered_events) == 5, "Failed to filter by authors"
async def filter_by_tag_p(all_events: List[NostrEvent], author): async def filter_by_tag_p(all_events: list[NostrEvent], author):
# todo: check why constructor does not work for fields with aliases (#e, #p) # todo: check why constructor does not work for fields with aliases (#e, #p)
nostr_filter = NostrFilter() nostr_filter = NostrFilter()
nostr_filter.p.append(author) nostr_filter.p.append(author)
@ -102,7 +101,7 @@ async def filter_by_tag_p(all_events: List[NostrEvent], author):
assert len(filtered_events) == 5, "Failed to filter by tag 'p'" assert len(filtered_events) == 5, "Failed to filter by tag 'p'"
async def filter_by_tag_e(all_events: List[NostrEvent], event_id): async def filter_by_tag_e(all_events: list[NostrEvent], event_id):
nostr_filter = NostrFilter() nostr_filter = NostrFilter()
nostr_filter.e.append(event_id) nostr_filter.e.append(event_id)
@ -114,7 +113,7 @@ async def filter_by_tag_e(all_events: List[NostrEvent], event_id):
async def filter_by_tag_e_and_p( async def filter_by_tag_e_and_p(
all_events: List[NostrEvent], author, event_id, reply_event_id all_events: list[NostrEvent], author, event_id, reply_event_id
): ):
nostr_filter = NostrFilter() nostr_filter = NostrFilter()
nostr_filter.p.append(author) nostr_filter.p.append(author)
@ -134,7 +133,7 @@ async def filter_by_tag_e_and_p(
async def filter_by_tag_e_p_and_author( async def filter_by_tag_e_p_and_author(
all_events: List[NostrEvent], author, event_id, reply_event_id all_events: list[NostrEvent], author, event_id, reply_event_id
): ):
nostr_filter = NostrFilter(authors=[author]) nostr_filter = NostrFilter(authors=[author])
nostr_filter.p.append(author) nostr_filter.p.append(author)

2293
uv.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
@ -138,7 +137,7 @@ async def api_get_relay_info() -> JSONResponse:
@nostrrelay_api_router.get("/api/v1/relay/{relay_id}") @nostrrelay_api_router.get("/api/v1/relay/{relay_id}")
async def api_get_relay( async def api_get_relay(
relay_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key) relay_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
) -> Optional[NostrRelay]: ) -> NostrRelay | None:
relay = await get_relay(wallet.wallet.user, relay_id) relay = await get_relay(wallet.wallet.user, relay_id)
if not relay: if not relay:
raise HTTPException( raise HTTPException(