mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-23 06:57:59 +00:00
Compare commits
6 commits
b8b9f59893
...
cc76256b12
Author | SHA1 | Date | |
---|---|---|---|
cc76256b12 | |||
7f0a1a4e08 | |||
ca70c1b293 | |||
619b1d5560 | |||
cf44b0dafe | |||
0cea1ff9e9 |
|
@ -18,7 +18,7 @@ dependencies = [
|
||||||
"aiohttp >= 3.9.5",
|
"aiohttp >= 3.9.5",
|
||||||
"aiohttp-swagger[performance] == 1.0.16",
|
"aiohttp-swagger[performance] == 1.0.16",
|
||||||
"argon2-cffi == 23.1.0",
|
"argon2-cffi == 23.1.0",
|
||||||
"barkshark-lib >= 0.2.2.post2, < 0.3.0",
|
"barkshark-lib >= 0.2.3, < 0.3.0",
|
||||||
"barkshark-sql >= 0.2.0, < 0.3.0",
|
"barkshark-sql >= 0.2.0, < 0.3.0",
|
||||||
"click == 8.1.2",
|
"click == 8.1.2",
|
||||||
"hiredis == 2.3.2",
|
"hiredis == 2.3.2",
|
||||||
|
|
|
@ -12,6 +12,7 @@ from aiohttp.web import HTTPException, StaticResource
|
||||||
from aiohttp_swagger import setup_swagger
|
from aiohttp_swagger import setup_swagger
|
||||||
from aputils.signer import Signer
|
from aputils.signer import Signer
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from blib import HttpError
|
||||||
from bsql import Database
|
from bsql import Database
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
@ -26,8 +27,7 @@ from .config import Config
|
||||||
from .database import Connection, get_database
|
from .database import Connection, get_database
|
||||||
from .database.schema import Instance
|
from .database.schema import Instance
|
||||||
from .http_client import HttpClient
|
from .http_client import HttpClient
|
||||||
from .misc import HttpError, Message, Response, check_open_port, get_resource
|
from .misc import JSON_PATHS, TOKEN_PATHS, Message, Response, check_open_port, get_resource
|
||||||
from .misc import JSON_PATHS, TOKEN_PATHS
|
|
||||||
from .template import Template
|
from .template import Template
|
||||||
from .views import VIEWS
|
from .views import VIEWS
|
||||||
from .views.api import handle_api_path
|
from .views.api import handle_api_path
|
||||||
|
@ -296,7 +296,7 @@ def format_error(request: web.Request, error: HttpError) -> Response:
|
||||||
app: Application = request.app # type: ignore[assignment]
|
app: Application = request.app # type: ignore[assignment]
|
||||||
|
|
||||||
if request.path.startswith(JSON_PATHS) or 'json' in request.headers.get('accept', ''):
|
if request.path.startswith(JSON_PATHS) or 'json' in request.headers.get('accept', ''):
|
||||||
return Response.new({'error': error.body}, error.status, ctype = 'json')
|
return Response.new({'error': error.message}, error.status, ctype = 'json')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
body = app.template.render('page/error.haml', request, e = error)
|
body = app.template.render('page/error.haml', request, e = error)
|
||||||
|
@ -338,21 +338,21 @@ async def handle_response_headers(
|
||||||
except HttpError as e:
|
except HttpError as e:
|
||||||
resp = format_error(request, e)
|
resp = format_error(request, e)
|
||||||
|
|
||||||
except HTTPException as ae:
|
except HTTPException as e:
|
||||||
if ae.status == 404:
|
if e.status == 404:
|
||||||
try:
|
try:
|
||||||
text = (ae.text or "").split(":")[1].strip()
|
text = (e.text or "").split(":")[1].strip()
|
||||||
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
text = ae.text or ""
|
text = e.text or ""
|
||||||
|
|
||||||
resp = format_error(request, HttpError(ae.status, text))
|
resp = format_error(request, HttpError(e.status, text))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
resp = format_error(request, HttpError(500, f'{type(e).__name__}: {str(e)}'))
|
resp = format_error(request, HttpError(500, 'Internal server error'))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
resp.headers['Server'] = 'ActivityRelay'
|
resp.headers['Server'] = 'ActivityRelay'
|
||||||
|
|
|
@ -8,7 +8,7 @@ from blib import Date
|
||||||
from bsql import Database, Row
|
from bsql import Database, Row
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta, timezone
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
from typing import TYPE_CHECKING, Any, TypedDict
|
from typing import TYPE_CHECKING, Any, TypedDict
|
||||||
|
|
||||||
|
@ -72,6 +72,9 @@ class Item:
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
self.updated = Date.parse(self.updated)
|
self.updated = Date.parse(self.updated)
|
||||||
|
|
||||||
|
if self.updated.tzinfo is None:
|
||||||
|
self.updated = self.updated.replace(tzinfo = timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_data(cls: type[Item], *args: Any) -> Item:
|
def from_data(cls: type[Item], *args: Any) -> Item:
|
||||||
|
@ -82,8 +85,7 @@ class Item:
|
||||||
|
|
||||||
|
|
||||||
def older_than(self, hours: int) -> bool:
|
def older_than(self, hours: int) -> bool:
|
||||||
delta = Date.new_utc() - self.updated
|
return self.updated + timedelta(hours = hours) < Date.new_utc()
|
||||||
return (delta.total_seconds()) > hours * 3600
|
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
|
|
@ -4,6 +4,7 @@ from blib import Date
|
||||||
from bsql import Column, Row, Tables
|
from bsql import Column, Row, Tables
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from datetime import timezone
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from .config import ConfigData
|
from .config import ConfigData
|
||||||
|
@ -18,12 +19,15 @@ TABLES = Tables()
|
||||||
|
|
||||||
def deserialize_timestamp(value: Any) -> Date:
|
def deserialize_timestamp(value: Any) -> Date:
|
||||||
try:
|
try:
|
||||||
return Date.parse(value)
|
date = Date.parse(value)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
date = Date.fromisoformat(value)
|
||||||
|
|
||||||
return Date.fromisoformat(value)
|
if date.tzinfo is None:
|
||||||
|
date = date.replace(tzinfo = timezone.utc)
|
||||||
|
|
||||||
|
return date
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
|
@ -45,14 +49,16 @@ class Instance(Row):
|
||||||
followid: Column[str] = Column('followid', 'text')
|
followid: Column[str] = Column('followid', 'text')
|
||||||
software: Column[str] = Column('software', 'text')
|
software: Column[str] = Column('software', 'text')
|
||||||
accepted: Column[Date] = Column('accepted', 'boolean')
|
accepted: Column[Date] = Column('accepted', 'boolean')
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
class Whitelist(Row):
|
class Whitelist(Row):
|
||||||
domain: Column[str] = Column(
|
domain: Column[str] = Column(
|
||||||
'domain', 'text', primary_key = True, unique = True, nullable = True)
|
'domain', 'text', primary_key = True, unique = True, nullable = True)
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
|
@ -64,7 +70,8 @@ class DomainBan(Row):
|
||||||
'domain', 'text', primary_key = True, unique = True, nullable = True)
|
'domain', 'text', primary_key = True, unique = True, nullable = True)
|
||||||
reason: Column[str] = Column('reason', 'text')
|
reason: Column[str] = Column('reason', 'text')
|
||||||
note: Column[str] = Column('note', 'text')
|
note: Column[str] = Column('note', 'text')
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
|
@ -75,7 +82,8 @@ class SoftwareBan(Row):
|
||||||
name: Column[str] = Column('name', 'text', primary_key = True, unique = True, nullable = True)
|
name: Column[str] = Column('name', 'text', primary_key = True, unique = True, nullable = True)
|
||||||
reason: Column[str] = Column('reason', 'text')
|
reason: Column[str] = Column('reason', 'text')
|
||||||
note: Column[str] = Column('note', 'text')
|
note: Column[str] = Column('note', 'text')
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
|
@ -87,7 +95,8 @@ class User(Row):
|
||||||
'username', 'text', primary_key = True, unique = True, nullable = False)
|
'username', 'text', primary_key = True, unique = True, nullable = False)
|
||||||
hash: Column[str] = Column('hash', 'text', nullable = False)
|
hash: Column[str] = Column('hash', 'text', nullable = False)
|
||||||
handle: Column[str] = Column('handle', 'text')
|
handle: Column[str] = Column('handle', 'text')
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
@TABLES.add_row
|
@TABLES.add_row
|
||||||
|
@ -104,8 +113,10 @@ class App(Row):
|
||||||
token: Column[str | None] = Column('token', 'text')
|
token: Column[str | None] = Column('token', 'text')
|
||||||
auth_code: Column[str | None] = Column('auth_code', 'text')
|
auth_code: Column[str | None] = Column('auth_code', 'text')
|
||||||
user: Column[str | None] = Column('user', 'text')
|
user: Column[str | None] = Column('user', 'text')
|
||||||
created: Column[Date] = Column('created', 'timestamp', nullable = False)
|
created: Column[Date] = Column(
|
||||||
accessed: Column[Date] = Column('accessed', 'timestamp', nullable = False)
|
'created', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
accessed: Column[Date] = Column(
|
||||||
|
'accessed', 'timestamp', nullable = False, deserializer = deserialize_timestamp)
|
||||||
|
|
||||||
|
|
||||||
def get_api_data(self, include_token: bool = False) -> dict[str, Any]:
|
def get_api_data(self, include_token: bool = False) -> dict[str, Any]:
|
||||||
|
|
2
relay/errors.py
Normal file
2
relay/errors.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class EmptyBodyError(Exception):
|
||||||
|
pass
|
|
@ -4,4 +4,4 @@
|
||||||
-block content
|
-block content
|
||||||
.section.error
|
.section.error
|
||||||
.title << HTTP Error {{e.status}}
|
.title << HTTP Error {{e.status}}
|
||||||
.body -> =e.body
|
.body -> =e.message
|
||||||
|
|
|
@ -4,12 +4,13 @@ import json
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
||||||
from aputils import AlgorithmType, Nodeinfo, ObjectType, Signer, WellKnownNodeinfo
|
from aputils import AlgorithmType, Nodeinfo, ObjectType, Signer, WellKnownNodeinfo
|
||||||
from blib import JsonBase
|
from blib import HttpError, JsonBase
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
||||||
|
|
||||||
from . import __version__, logger as logging
|
from . import __version__, logger as logging
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .database.schema import Instance
|
from .database.schema import Instance
|
||||||
|
from .errors import EmptyBodyError
|
||||||
from .misc import MIMETYPES, Message, get_app
|
from .misc import MIMETYPES, Message, get_app
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -107,11 +108,7 @@ class HttpClient:
|
||||||
if not self._session:
|
if not self._session:
|
||||||
raise RuntimeError('Client not open')
|
raise RuntimeError('Client not open')
|
||||||
|
|
||||||
try:
|
url = url.split("#", 1)[0]
|
||||||
url, _ = url.split('#', 1)
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not force:
|
if not force:
|
||||||
try:
|
try:
|
||||||
|
@ -136,10 +133,17 @@ class HttpClient:
|
||||||
|
|
||||||
data = await resp.text()
|
data = await resp.text()
|
||||||
|
|
||||||
if resp.status != 200:
|
if resp.status not in (200, 202):
|
||||||
logging.verbose('Received error when requesting %s: %i', url, resp.status)
|
logging.verbose('Received error when requesting %s: %i', url, resp.status)
|
||||||
logging.debug(data)
|
logging.debug(data)
|
||||||
return None
|
|
||||||
|
try:
|
||||||
|
error = json.loads(data)["error"]
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
error = data
|
||||||
|
|
||||||
|
raise HttpError(resp.status, error)
|
||||||
|
|
||||||
self.cache.set('request', url, data, 'str')
|
self.cache.set('request', url, data, 'str')
|
||||||
return data
|
return data
|
||||||
|
@ -151,7 +155,7 @@ class HttpClient:
|
||||||
sign_headers: bool,
|
sign_headers: bool,
|
||||||
cls: None = None,
|
cls: None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
old_algo: bool = True) -> None: ...
|
old_algo: bool = True) -> str | None: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -168,7 +172,7 @@ class HttpClient:
|
||||||
sign_headers: bool,
|
sign_headers: bool,
|
||||||
cls: type[T] | None = None,
|
cls: type[T] | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
old_algo: bool = True) -> T | None:
|
old_algo: bool = True) -> T | str | None:
|
||||||
|
|
||||||
if cls is not None and not issubclass(cls, JsonBase):
|
if cls is not None and not issubclass(cls, JsonBase):
|
||||||
raise TypeError('cls must be a sub-class of "blib.JsonBase"')
|
raise TypeError('cls must be a sub-class of "blib.JsonBase"')
|
||||||
|
@ -177,11 +181,12 @@ class HttpClient:
|
||||||
|
|
||||||
if cls is not None:
|
if cls is not None:
|
||||||
if data is None:
|
if data is None:
|
||||||
raise ValueError("Empty response")
|
# this shouldn't actually get raised, but keeping just in case
|
||||||
|
raise EmptyBodyError(f"GET {url}")
|
||||||
|
|
||||||
return cls.parse(data)
|
return cls.parse(data)
|
||||||
|
|
||||||
return None
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def post(self, url: str, data: Message | bytes, instance: Instance | None = None) -> None:
|
async def post(self, url: str, data: Message | bytes, instance: Instance | None = None) -> None:
|
||||||
|
|
|
@ -224,6 +224,9 @@ def cli_db_maintenance(ctx: click.Context, fix_timestamps: bool) -> None:
|
||||||
with ctx.obj.database.session(True) as conn:
|
with ctx.obj.database.session(True) as conn:
|
||||||
conn.fix_timestamps()
|
conn.fix_timestamps()
|
||||||
|
|
||||||
|
if ctx.obj.config.db_type == "postgres":
|
||||||
|
return
|
||||||
|
|
||||||
with ctx.obj.database.session(False) as conn:
|
with ctx.obj.database.session(False) as conn:
|
||||||
with conn.execute("VACUUM"):
|
with conn.execute("VACUUM"):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -134,17 +134,6 @@ def get_resource(path: str) -> Path:
|
||||||
return Path(str(pkgfiles('relay'))).joinpath(path)
|
return Path(str(pkgfiles('relay'))).joinpath(path)
|
||||||
|
|
||||||
|
|
||||||
class HttpError(Exception):
|
|
||||||
def __init__(self,
|
|
||||||
status: int,
|
|
||||||
body: str) -> None:
|
|
||||||
|
|
||||||
self.body: str = body
|
|
||||||
self.status: int = status
|
|
||||||
|
|
||||||
Exception.__init__(self, f"HTTP Error {status}: {body}")
|
|
||||||
|
|
||||||
|
|
||||||
class JsonEncoder(json.JSONEncoder):
|
class JsonEncoder(json.JSONEncoder):
|
||||||
def default(self, o: Any) -> str:
|
def default(self, o: Any) -> str:
|
||||||
if isinstance(o, datetime):
|
if isinstance(o, datetime):
|
||||||
|
|
|
@ -2,12 +2,13 @@ import aputils
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
|
from blib import HttpError
|
||||||
|
|
||||||
from .base import View, register_route
|
from .base import View, register_route
|
||||||
|
|
||||||
from .. import logger as logging
|
from .. import logger as logging
|
||||||
from ..database import schema
|
from ..database import schema
|
||||||
from ..misc import HttpError, Message, Response
|
from ..misc import Message, Response
|
||||||
from ..processors import run_processor
|
from ..processors import run_processor
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,15 +94,19 @@ class ActorView(View):
|
||||||
try:
|
try:
|
||||||
self.actor = await self.client.get(self.signature.keyid, True, Message)
|
self.actor = await self.client.get(self.signature.keyid, True, Message)
|
||||||
|
|
||||||
except Exception:
|
except HttpError:
|
||||||
# ld signatures aren't handled atm, so just ignore it
|
# ld signatures aren't handled atm, so just ignore it
|
||||||
if self.message.type == 'Delete':
|
if self.message.type == 'Delete':
|
||||||
logging.verbose('Instance sent a delete which cannot be handled')
|
logging.verbose('Instance sent a delete which cannot be handled')
|
||||||
raise HttpError(202, '')
|
raise HttpError(202, '')
|
||||||
|
|
||||||
logging.verbose(f'Failed to fetch actor: {self.signature.keyid}')
|
logging.verbose('Failed to fetch actor: %s', self.signature.keyid)
|
||||||
raise HttpError(400, 'failed to fetch actor')
|
raise HttpError(400, 'failed to fetch actor')
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise HttpError(500, 'unexpected error when fetching actor')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.signer = self.actor.signer
|
self.signer = self.actor.signer
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import traceback
|
||||||
|
|
||||||
from aiohttp.web import Request, middleware
|
from aiohttp.web import Request, middleware
|
||||||
from argon2.exceptions import VerifyMismatchError
|
from argon2.exceptions import VerifyMismatchError
|
||||||
from blib import convert_to_boolean
|
from blib import HttpError, convert_to_boolean
|
||||||
from collections.abc import Awaitable, Callable, Sequence
|
from collections.abc import Awaitable, Callable, Sequence
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from .base import View, register_route
|
||||||
|
|
||||||
from .. import __version__
|
from .. import __version__
|
||||||
from ..database import ConfigData, schema
|
from ..database import ConfigData, schema
|
||||||
from ..misc import HttpError, Message, Response, boolean
|
from ..misc import Message, Response, boolean
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_REDIRECT: str = 'urn:ietf:wg:oauth:2.0:oob'
|
DEFAULT_REDIRECT: str = 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
@ -324,7 +324,7 @@ class Inbox(View):
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise HttpError(500, 'Failed to fetch actor')
|
raise HttpError(500, 'Failed to fetch actor') from None
|
||||||
|
|
||||||
data['inbox'] = actor_data.shared_inbox
|
data['inbox'] = actor_data.shared_inbox
|
||||||
|
|
||||||
|
@ -396,7 +396,7 @@ class RequestView(View):
|
||||||
instance = conn.put_request_response(data['domain'], boolean(data['accept']))
|
instance = conn.put_request_response(data['domain'], boolean(data['accept']))
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise HttpError(404, 'Request not found')
|
raise HttpError(404, 'Request not found') from None
|
||||||
|
|
||||||
message = Message.new_response(
|
message = Message.new_response(
|
||||||
host = self.config.domain,
|
host = self.config.domain,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
from aiohttp.abc import AbstractView
|
from aiohttp.abc import AbstractView
|
||||||
from aiohttp.hdrs import METH_ALL as METHODS
|
from aiohttp.hdrs import METH_ALL as METHODS
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
|
from blib import HttpError
|
||||||
from bsql import Database
|
from bsql import Database
|
||||||
from collections.abc import Awaitable, Callable, Generator, Sequence, Mapping
|
from collections.abc import Awaitable, Callable, Generator, Sequence, Mapping
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
@ -13,7 +14,7 @@ from ..cache import Cache
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from ..database import Connection
|
from ..database import Connection
|
||||||
from ..http_client import HttpClient
|
from ..http_client import HttpClient
|
||||||
from ..misc import HttpError, Response, get_app
|
from ..misc import Response, get_app
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
@ -137,7 +138,7 @@ class View(AbstractView):
|
||||||
data[key] = post_data[key]
|
data[key] = post_data[key]
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise HttpError(400, f'Missing {str(e)} pararmeter')
|
raise HttpError(400, f'Missing {str(e)} pararmeter') from None
|
||||||
|
|
||||||
for key in optional:
|
for key in optional:
|
||||||
data[key] = post_data.get(key) # type: ignore[assignment]
|
data[key] = post_data.get(key) # type: ignore[assignment]
|
||||||
|
|
Loading…
Reference in a new issue