little-boxes/little_boxes/backend.py

129 wiersze
3.9 KiB
Python

2018-06-13 18:08:12 +00:00
import abc
2018-06-16 19:57:07 +00:00
import binascii
2018-07-08 21:02:15 +00:00
import json
2018-06-16 19:58:48 +00:00
import os
import typing
from typing import Any
from typing import Dict
2018-07-08 10:21:15 +00:00
from typing import List
from typing import Optional
2018-06-16 12:19:47 +00:00
import requests
from .__version__ import __version__
from .collection import parse_collection
2018-07-09 22:23:55 +00:00
from .errors import ActivityGoneError
2018-06-16 20:16:33 +00:00
from .errors import ActivityNotFoundError
2018-07-08 21:02:15 +00:00
from .errors import ActivityUnavailableError
2018-07-22 09:18:46 +00:00
from .errors import NotAnActivityError
2018-07-08 21:02:15 +00:00
from .urlutils import URLLookupFailedError
2018-06-24 09:33:14 +00:00
from .urlutils import check_url as check_url
2018-06-13 18:08:12 +00:00
if typing.TYPE_CHECKING:
2018-06-13 18:09:58 +00:00
from little_boxes import activitypub as ap # noqa: type checking
2018-06-13 18:08:12 +00:00
class Backend(abc.ABC):
2018-06-24 09:33:14 +00:00
def debug_mode(self) -> bool:
"""Should be overidded to return `True` in order to enable the debug mode."""
return False
def check_url(self, url: str) -> None:
check_url(url, debug=self.debug_mode())
2018-06-16 12:19:47 +00:00
def user_agent(self) -> str:
2018-06-22 20:57:38 +00:00
return (
f"{requests.utils.default_user_agent()} (Little Boxes/{__version__};"
" +http://github.com/tsileo/little-boxes)"
)
2018-06-16 12:19:47 +00:00
2018-06-16 19:57:07 +00:00
def random_object_id(self) -> str:
"""Generates a random object ID."""
return binascii.hexlify(os.urandom(8)).decode("utf-8")
2018-06-16 16:54:17 +00:00
def fetch_json(self, url: str, **kwargs):
2018-06-24 09:33:14 +00:00
self.check_url(url)
2018-06-16 12:19:47 +00:00
resp = requests.get(
2018-06-16 16:54:17 +00:00
url,
headers={"User-Agent": self.user_agent(), "Accept": "application/json"},
**kwargs,
2018-07-20 22:03:49 +00:00
timeout=15,
2018-07-21 09:29:37 +00:00
allow_redirects=True,
2018-06-16 12:19:47 +00:00
)
2018-06-22 21:38:25 +00:00
resp.raise_for_status()
2018-06-16 16:54:17 +00:00
return resp
2018-06-16 12:19:47 +00:00
def parse_collection(
self, payload: Optional[Dict[str, Any]] = None, url: Optional[str] = None
2018-07-08 10:21:15 +00:00
) -> List[str]:
return parse_collection(payload=payload, url=url, fetcher=self.fetch_iri)
2018-07-29 12:41:59 +00:00
def extra_inboxes(self) -> List[str]:
"""Allows to define inboxes that will be part of of the recipient for every activity."""
return []
2018-06-16 19:58:48 +00:00
def is_from_outbox(
self, as_actor: "ap.Person", activity: "ap.BaseActivity"
) -> bool:
2018-06-16 19:57:07 +00:00
return activity.get_actor().id == as_actor.id
2018-06-16 09:17:15 +00:00
@abc.abstractmethod
def base_url(self) -> str:
2018-06-16 16:54:17 +00:00
pass # pragma: no cover
2018-06-16 09:17:15 +00:00
2018-06-16 20:16:33 +00:00
def fetch_iri(self, iri: str, **kwargs) -> "ap.ObjectType": # pragma: no cover
2018-07-22 09:18:46 +00:00
if not iri.startswith("http"):
2018-07-22 09:18:27 +00:00
raise NotAnActivityError(f"{iri} is not a valid IRI")
2018-07-08 21:02:15 +00:00
try:
self.check_url(iri)
except URLLookupFailedError:
2018-07-09 22:23:35 +00:00
# The IRI is inaccessible
2018-07-08 21:02:15 +00:00
raise ActivityUnavailableError(f"unable to fetch {iri}, url lookup failed")
try:
resp = requests.get(
iri,
headers={
"User-Agent": self.user_agent(),
"Accept": "application/activity+json",
},
timeout=15,
2018-07-09 22:23:35 +00:00
allow_redirects=False,
2018-07-08 21:02:15 +00:00
**kwargs,
)
except (
requests.exceptions.ConnectTimeout,
requests.exceptions.ReadTimeout,
requests.exceptions.ConnectionError,
):
raise ActivityUnavailableError(f"unable to fetch {iri}, connection error")
2018-06-16 20:16:33 +00:00
if resp.status_code == 404:
raise ActivityNotFoundError(f"{iri} is not found")
2018-06-22 21:38:25 +00:00
elif resp.status_code == 410:
2018-07-09 22:23:35 +00:00
raise ActivityGoneError(f"{iri} is gone")
2018-07-08 21:02:15 +00:00
elif resp.status_code in [500, 502, 503]:
raise ActivityUnavailableError(
f"unable to fetch {iri}, server error ({resp.status_code})"
)
2018-06-16 20:16:33 +00:00
resp.raise_for_status()
2018-07-08 21:02:15 +00:00
try:
out = resp.json()
2018-07-22 09:18:27 +00:00
except (json.JSONDecodeError, ValueError):
2018-07-08 21:02:15 +00:00
# TODO(tsileo): a special error type?
2018-07-22 09:18:27 +00:00
raise NotAnActivityError(f"{iri} is not JSON")
2018-07-08 21:02:15 +00:00
return out
2018-06-13 18:08:12 +00:00
@abc.abstractmethod
def activity_url(self, obj_id: str) -> str:
2018-06-16 16:54:17 +00:00
pass # pragma: no cover
2018-06-13 18:08:12 +00:00
2018-06-22 23:14:05 +00:00
@abc.abstractmethod
def note_url(self, obj_id: str) -> str:
pass # pragma: no cover