From 188e5a244689d5acec04ef5b9f73eb30eed13167 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Mon, 17 Jul 2023 00:37:47 -0600 Subject: [PATCH] Remove all remaining async code for now --- activities/models/emoji.py | 7 +-- activities/models/fan_out.py | 15 +++-- activities/models/post.py | 7 +-- activities/services/search.py | 7 +-- activities/views/debug.py | 7 +-- core/files.py | 8 +-- core/signatures.py | 6 +- tests/conftest.py | 4 -- tests/core/test_signatures.py | 3 +- tests/users/models/test_identity.py | 14 ++--- tests/users/models/test_system_actor.py | 3 +- users/admin.py | 3 +- users/models/block.py | 35 ++++------- users/models/domain.py | 17 +++-- users/models/follow.py | 43 +++++-------- users/models/identity.py | 84 +++++++++++-------------- users/models/report.py | 29 +++------ users/models/system_actor.py | 4 +- users/views/activitypub.py | 3 +- 19 files changed, 114 insertions(+), 185 deletions(-) diff --git a/activities/models/emoji.py b/activities/models/emoji.py index c1ba84b..50a3321 100644 --- a/activities/models/emoji.py +++ b/activities/models/emoji.py @@ -4,7 +4,6 @@ from typing import ClassVar import httpx import urlman -from asgiref.sync import sync_to_async from cachetools import TTLCache, cached from django.conf import settings from django.core.exceptions import ValidationError @@ -35,13 +34,13 @@ class EmojiStates(StateGraph): outdated.transitions_to(updated) @classmethod - async def handle_outdated(cls, instance: "Emoji"): + def handle_outdated(cls, instance: "Emoji"): """ Fetches remote emoji and uploads to file for local caching """ if instance.remote_url and not instance.file: try: - file, mimetype = await get_remote_file( + file, mimetype = get_remote_file( instance.remote_url, timeout=settings.SETUP.REMOTE_TIMEOUT, max_size=settings.SETUP.EMOJI_MAX_IMAGE_FILESIZE_KB * 1024, @@ -55,7 +54,7 @@ class EmojiStates(StateGraph): instance.file = file instance.mimetype = mimetype - await sync_to_async(instance.save)() + instance.save() return cls.updated diff --git a/activities/models/fan_out.py b/activities/models/fan_out.py index 242de12..0d5a72e 100644 --- a/activities/models/fan_out.py +++ b/activities/models/fan_out.py @@ -1,5 +1,4 @@ import httpx -from asgiref.sync import async_to_sync from django.db import models from activities.models.timeline_event import TimelineEvent @@ -77,7 +76,7 @@ class FanOutStates(StateGraph): post = instance.subject_post # Sign it and send it try: - async_to_sync(post.author.signed_request)( + post.author.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -93,7 +92,7 @@ class FanOutStates(StateGraph): post = instance.subject_post # Sign it and send it try: - async_to_sync(post.author.signed_request)( + post.author.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -119,7 +118,7 @@ class FanOutStates(StateGraph): post = instance.subject_post # Send it to the remote inbox try: - async_to_sync(post.author.signed_request)( + post.author.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -172,7 +171,7 @@ class FanOutStates(StateGraph): body = interaction.to_add_ap() else: body = interaction.to_create_ap() - async_to_sync(interaction.identity.signed_request)( + interaction.identity.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -202,7 +201,7 @@ class FanOutStates(StateGraph): body = interaction.to_remove_ap() else: body = interaction.to_undo_ap() - async_to_sync(interaction.identity.signed_request)( + interaction.identity.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -217,7 +216,7 @@ class FanOutStates(StateGraph): case (FanOut.Types.identity_edited, False): identity = instance.subject_identity try: - async_to_sync(identity.signed_request)( + identity.signed_request( method="post", uri=( instance.identity.shared_inbox_uri @@ -232,7 +231,7 @@ class FanOutStates(StateGraph): case (FanOut.Types.identity_deleted, False): identity = instance.subject_identity try: - async_to_sync(identity.signed_request)( + identity.signed_request( method="post", uri=( instance.identity.shared_inbox_uri diff --git a/activities/models/post.py b/activities/models/post.py index f6ae6e8..f764d07 100644 --- a/activities/models/post.py +++ b/activities/models/post.py @@ -8,7 +8,6 @@ from urllib.parse import urlparse import httpx import urlman -from asgiref.sync import async_to_sync from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVector from django.db import models, transaction @@ -831,7 +830,7 @@ class Post(StatorModel): # If the author is not fetched yet, try again later if author.domain is None: if fetch_author: - async_to_sync(author.fetch_actor)() + author.fetch_actor() # perhaps the entire "try again" logic below # could be replaced with TryAgainLater for # _all_ fetches, to let it handle pinned posts? @@ -981,7 +980,7 @@ class Post(StatorModel): except cls.DoesNotExist: if fetch: try: - response = async_to_sync(SystemActor().signed_request)( + response = SystemActor().signed_request( method="get", uri=object_uri ) except (httpx.HTTPError, ssl.SSLCertVerificationError): @@ -1008,7 +1007,7 @@ class Post(StatorModel): ) from err # We may need to fetch the author too if post.author.state == IdentityStates.outdated: - async_to_sync(post.author.fetch_actor)() + post.author.fetch_actor() return post else: raise cls.DoesNotExist(f"Cannot find Post with URI {object_uri}") diff --git a/activities/services/search.py b/activities/services/search.py index bb01eb6..0038c84 100644 --- a/activities/services/search.py +++ b/activities/services/search.py @@ -1,5 +1,4 @@ import httpx -from asgiref.sync import async_to_sync from activities.models import Hashtag, Post from core.ld import canonicalise @@ -49,7 +48,7 @@ class SearchService: username, domain_instance or domain, fetch=True ) if identity and identity.state == IdentityStates.outdated: - async_to_sync(identity.fetch_actor)() + identity.fetch_actor() except ValueError: pass @@ -74,7 +73,7 @@ class SearchService: # Fetch the provided URL as the system actor to retrieve the AP JSON try: - response = async_to_sync(SystemActor().signed_request)( + response = SystemActor().signed_request( method="get", uri=self.query, ) @@ -90,7 +89,7 @@ class SearchService: # Try and retrieve the profile by actor URI identity = Identity.by_actor_uri(document["id"], create=True) if identity and identity.state == IdentityStates.outdated: - async_to_sync(identity.fetch_actor)() + identity.fetch_actor() return identity # Is it a post? diff --git a/activities/views/debug.py b/activities/views/debug.py index 9cb4393..d838905 100644 --- a/activities/views/debug.py +++ b/activities/views/debug.py @@ -1,7 +1,6 @@ import json import httpx -from asgiref.sync import async_to_sync from django import forms from django.utils.decorators import method_decorator from django.views.generic import FormView, TemplateView @@ -13,7 +12,6 @@ from users.models import SystemActor @method_decorator(admin_required, name="dispatch") class JsonViewer(FormView): - template_name = "activities/debug_json.html" class form_class(forms.Form): @@ -31,7 +29,7 @@ class JsonViewer(FormView): context = self.get_context_data(form=form) try: - response = async_to_sync(SystemActor().signed_request)( + response = SystemActor().signed_request( method="get", uri=uri, ) @@ -64,18 +62,15 @@ class JsonViewer(FormView): class NotFound(TemplateView): - template_name = "404.html" class ServerError(TemplateView): - template_name = "500.html" @method_decorator(admin_required, name="dispatch") class OauthAuthorize(TemplateView): - template_name = "api/oauth_authorize.html" def get_context_data(self): diff --git a/core/files.py b/core/files.py index 8a0c5c0..07f19fc 100644 --- a/core/files.py +++ b/core/files.py @@ -57,7 +57,7 @@ def blurhash_image(file) -> str: return blurhash.encode(file, 4, 4) -async def get_remote_file( +def get_remote_file( url: str, *, timeout: float = settings.SETUP.REMOTE_TIMEOUT, @@ -70,8 +70,8 @@ async def get_remote_file( "User-Agent": settings.TAKAHE_USER_AGENT, } - async with httpx.AsyncClient(headers=headers) as client: - async with client.stream( + with httpx.Client(headers=headers) as client: + with client.stream( "GET", url, timeout=timeout, follow_redirects=True ) as stream: allow_download = max_size is None @@ -82,7 +82,7 @@ async def get_remote_file( except (KeyError, TypeError): pass if allow_download: - file = ContentFile(await stream.aread(), name=url) + file = ContentFile(stream.read(), name=url) return file, stream.headers.get( "content-type", "application/octet-stream" ) diff --git a/core/signatures.py b/core/signatures.py index 5b3c96f..333b98d 100644 --- a/core/signatures.py +++ b/core/signatures.py @@ -177,7 +177,7 @@ class HttpSignature: ) @classmethod - async def signed_request( + def signed_request( cls, uri: str, body: dict | None, @@ -241,9 +241,9 @@ class HttpSignature: # Send the request with all those headers except the pseudo one del headers["(request-target)"] - async with httpx.AsyncClient(timeout=timeout) as client: + with httpx.Client(timeout=timeout) as client: try: - response = await client.request( + response = client.request( method, uri, headers=headers, diff --git a/tests/conftest.py b/tests/conftest.py index ea0861f..8429b28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -238,10 +238,6 @@ def stator(config_system) -> StatorRunner: """ Return an initialized StatorRunner for tests that need state transitioning to happen. - - Example: - # Do some tasks with state side effects - async_to_sync(stator_runner.fetch_and_process_tasks)() """ runner = StatorRunner( StatorModel.subclasses, diff --git a/tests/core/test_signatures.py b/tests/core/test_signatures.py index f15e090..f1c63a9 100644 --- a/tests/core/test_signatures.py +++ b/tests/core/test_signatures.py @@ -1,5 +1,4 @@ import pytest -from asgiref.sync import async_to_sync from django.test.client import RequestFactory from pytest_httpx import HTTPXMock @@ -75,7 +74,7 @@ def test_sign_http(httpx_mock: HTTPXMock, keypair): } # Send the signed request to the mock library httpx_mock.add_response() - async_to_sync(HttpSignature.signed_request)( + HttpSignature.signed_request( uri="https://example.com/test-actor", body=document, private_key=keypair["private_key"], diff --git a/tests/users/models/test_identity.py b/tests/users/models/test_identity.py index fbda49f..7e68795 100644 --- a/tests/users/models/test_identity.py +++ b/tests/users/models/test_identity.py @@ -1,5 +1,4 @@ import pytest -from asgiref.sync import async_to_sync from pytest_httpx import HTTPXMock from core.models import Config @@ -169,7 +168,7 @@ def test_fetch_actor(httpx_mock, config_system): "url": "https://example.com/test-actor/view/", }, ) - async_to_sync(identity.fetch_actor)() + identity.fetch_actor() # Verify the data arrived identity = Identity.objects.get(pk=identity.pk) @@ -189,15 +188,14 @@ def test_fetch_actor(httpx_mock, config_system): @pytest.mark.django_db -@pytest.mark.asyncio -async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system): +def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system): """ Ensures that we can deal with various kinds of webfinger URLs """ # With no host-meta, it should be the default assert ( - await Identity.fetch_webfinger_url("example.com") + Identity.fetch_webfinger_url("example.com") == "https://example.com/.well-known/webfinger?resource={uri}" ) @@ -210,7 +208,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system): """, ) assert ( - await Identity.fetch_webfinger_url("example.com") + Identity.fetch_webfinger_url("example.com") == "https://fedi.example.com/.well-known/webfinger?resource={uri}" ) @@ -223,7 +221,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system): """, ) assert ( - await Identity.fetch_webfinger_url("example.com") + Identity.fetch_webfinger_url("example.com") == "https://example.com/amazing-webfinger?query={uri}" ) @@ -237,7 +235,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system): """, ) assert ( - await Identity.fetch_webfinger_url("example.com") + Identity.fetch_webfinger_url("example.com") == "https://example.com/.well-known/webfinger?resource={uri}" ) diff --git a/tests/users/models/test_system_actor.py b/tests/users/models/test_system_actor.py index 855d2f6..b084035 100644 --- a/tests/users/models/test_system_actor.py +++ b/tests/users/models/test_system_actor.py @@ -1,5 +1,4 @@ import pytest -from asgiref.sync import async_to_sync from django.test.client import RequestFactory from pytest_httpx import HTTPXMock @@ -16,7 +15,7 @@ def test_system_actor_signed(config_system, httpx_mock: HTTPXMock): system_actor.generate_keys() # Send a fake outbound request httpx_mock.add_response() - async_to_sync(system_actor.signed_request)( + system_actor.signed_request( method="get", uri="http://example.com/test-actor", ) diff --git a/users/admin.py b/users/admin.py index 76b1ec7..4d920f2 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,4 +1,3 @@ -from asgiref.sync import async_to_sync from django.contrib import admin from django.db import models from django.utils import formats @@ -60,7 +59,7 @@ class DomainAdmin(admin.ModelAdmin): @admin.action(description="Fetch nodeinfo") def fetch_nodeinfo(self, request, queryset): for instance in queryset: - info = async_to_sync(instance.fetch_nodeinfo)() + info = instance.fetch_nodeinfo() if info: instance.nodeinfo = info.dict() instance.save() diff --git a/users/models/block.py b/users/models/block.py index 1871ab4..11b48e8 100644 --- a/users/models/block.py +++ b/users/models/block.py @@ -30,7 +30,7 @@ class BlockStates(StateGraph): return [cls.new, cls.sent, cls.awaiting_expiry] @classmethod - async def handle_new(cls, instance: "Block"): + def handle_new(cls, instance: "Block"): """ Block that are new need us to deliver the Block object to the target server. @@ -38,20 +38,18 @@ class BlockStates(StateGraph): # Mutes don't send but might need expiry if instance.mute: return cls.awaiting_expiry - # Fetch more info - block = await instance.afetch_full() # Remote blocks should not be here, local blocks just work - if not block.source.local or block.target.local: + if not instance.source.local or instance.target.local: return cls.sent # Don't try if the other identity didn't fetch yet - if not block.target.inbox_uri: + if not instance.target.inbox_uri: return # Sign it and send it try: - await block.source.signed_request( + instance.source.signed_request( method="post", - uri=block.target.inbox_uri, - body=canonicalise(block.to_ap()), + uri=instance.target.inbox_uri, + body=canonicalise(instance.to_ap()), ) except httpx.RequestError: return @@ -66,19 +64,18 @@ class BlockStates(StateGraph): return cls.undone @classmethod - async def handle_undone(cls, instance: "Block"): + def handle_undone(cls, instance: "Block"): """ Delivers the Undo object to the target server """ - block = await instance.afetch_full() # Remote blocks should not be here, mutes don't send, local blocks just work - if not block.source.local or block.target.local or instance.mute: + if not instance.source.local or instance.target.local or instance.mute: return cls.undone_sent try: - await block.source.signed_request( + instance.source.signed_request( method="post", - uri=block.target.inbox_uri, - body=canonicalise(block.to_undo_ap()), + uri=instance.target.inbox_uri, + body=canonicalise(instance.to_undo_ap()), ) except httpx.RequestError: return @@ -227,16 +224,6 @@ class Block(StatorModel): def active(self): return self.state in BlockStates.group_active() - ### Async helpers ### - - async def afetch_full(self): - """ - Returns a version of the object with all relations pre-loaded - """ - return await Block.objects.select_related( - "source", "source__domain", "target" - ).aget(pk=self.pk) - ### ActivityPub (outbound) ### def to_ap(self): diff --git a/users/models/domain.py b/users/models/domain.py index 99608bd..2d8808f 100644 --- a/users/models/domain.py +++ b/users/models/domain.py @@ -6,7 +6,6 @@ from typing import Optional import httpx import pydantic import urlman -from asgiref.sync import sync_to_async from django.conf import settings from django.db import models @@ -33,15 +32,15 @@ class DomainStates(StateGraph): outdated.times_out_to(connection_issue, 60 * 60 * 24) @classmethod - async def handle_outdated(cls, instance: "Domain"): - info = await instance.fetch_nodeinfo() + def handle_outdated(cls, instance: "Domain"): + info = instance.fetch_nodeinfo() if info: instance.nodeinfo = info.dict() - await sync_to_async(instance.save)() + instance.save() return cls.updated @classmethod - async def handle_updated(cls, instance: "Domain"): + def handle_updated(cls, instance: "Domain"): return cls.outdated @@ -157,18 +156,18 @@ class Domain(StatorModel): ) super().save(*args, **kwargs) - async def fetch_nodeinfo(self) -> NodeInfo | None: + def fetch_nodeinfo(self) -> NodeInfo | None: """ Fetch the /NodeInfo/2.0 for the domain """ nodeinfo20_url = f"https://{self.domain}/nodeinfo/2.0" - async with httpx.AsyncClient( + with httpx.Client( timeout=settings.SETUP.REMOTE_TIMEOUT, headers={"User-Agent": settings.TAKAHE_USER_AGENT}, ) as client: try: - response = await client.get( + response = client.get( f"https://{self.domain}/.well-known/nodeinfo", follow_redirects=True, headers={"Accept": "application/json"}, @@ -190,7 +189,7 @@ class Domain(StatorModel): pass try: - response = await client.get( + response = client.get( nodeinfo20_url, follow_redirects=True, headers={"Accept": "application/json"}, diff --git a/users/models/follow.py b/users/models/follow.py index b9f1fde..d63bcea 100644 --- a/users/models/follow.py +++ b/users/models/follow.py @@ -34,26 +34,25 @@ class FollowStates(StateGraph): return [cls.unrequested, cls.local_requested, cls.accepted] @classmethod - async def handle_unrequested(cls, instance: "Follow"): + def handle_unrequested(cls, instance: "Follow"): """ Follows that are unrequested need us to deliver the Follow object to the target server. """ - follow = await instance.afetch_full() # Remote follows should not be here - if not follow.source.local: + if not instance.source.local: return cls.remote_requested - if follow.target.local: + if instance.target.local: return cls.accepted # Don't try if the other identity didn't fetch yet - if not follow.target.inbox_uri: + if not instance.target.inbox_uri: return # Sign it and send it try: - await follow.source.signed_request( + instance.source.signed_request( method="post", - uri=follow.target.inbox_uri, - body=canonicalise(follow.to_ap()), + uri=instance.target.inbox_uri, + body=canonicalise(instance.to_ap()), ) except httpx.RequestError: return @@ -65,33 +64,31 @@ class FollowStates(StateGraph): pass @classmethod - async def handle_remote_requested(cls, instance: "Follow"): + def handle_remote_requested(cls, instance: "Follow"): """ Items in remote_requested need us to send an Accept object to the source server. """ - follow = await instance.afetch_full() try: - await follow.target.signed_request( + instance.target.signed_request( method="post", - uri=follow.source.inbox_uri, - body=canonicalise(follow.to_accept_ap()), + uri=instance.source.inbox_uri, + body=canonicalise(instance.to_accept_ap()), ) except httpx.RequestError: return return cls.accepted @classmethod - async def handle_undone(cls, instance: "Follow"): + def handle_undone(cls, instance: "Follow"): """ Delivers the Undo object to the target server """ - follow = await instance.afetch_full() try: - await follow.source.signed_request( + instance.source.signed_request( method="post", - uri=follow.target.inbox_uri, - body=canonicalise(follow.to_undo_ap()), + uri=instance.target.inbox_uri, + body=canonicalise(instance.to_undo_ap()), ) except httpx.RequestError: return @@ -204,16 +201,6 @@ class Follow(StatorModel): follow.save() return follow - ### Async helpers ### - - async def afetch_full(self): - """ - Returns a version of the object with all relations pre-loaded - """ - return await Follow.objects.select_related( - "source", "source__domain", "target" - ).aget(pk=self.pk) - ### Properties ### @property diff --git a/users/models/identity.py b/users/models/identity.py index 2b9adfc..aa1b34b 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -5,7 +5,6 @@ from urllib.parse import urlparse import httpx import urlman -from asgiref.sync import async_to_sync, sync_to_async from django.conf import settings from django.db import IntegrityError, models from django.utils import timezone @@ -66,13 +65,13 @@ class IdentityStates(StateGraph): return [cls.deleted, cls.deleted_fanned_out] @classmethod - async def targets_fan_out(cls, identity: "Identity", type_: str) -> None: + def targets_fan_out(cls, identity: "Identity", type_: str) -> None: from activities.models import FanOut from users.models import Follow # Fan out to each target shared_inboxes = set() - async for follower in Follow.objects.select_related("source", "target").filter( + for follower in Follow.objects.select_related("source", "target").filter( target=identity ): # Dedupe shared_inbox_uri @@ -80,7 +79,7 @@ class IdentityStates(StateGraph): if shared_uri and shared_uri in shared_inboxes: continue - await FanOut.objects.acreate( + FanOut.objects.create( identity=follower.source, type=type_, subject_identity=identity, @@ -88,34 +87,32 @@ class IdentityStates(StateGraph): shared_inboxes.add(shared_uri) @classmethod - async def handle_edited(cls, instance: "Identity"): + def handle_edited(cls, instance: "Identity"): from activities.models import FanOut if not instance.local: return cls.updated - identity = await instance.afetch_full() - await cls.targets_fan_out(identity, FanOut.Types.identity_edited) + cls.targets_fan_out(instance, FanOut.Types.identity_edited) return cls.updated @classmethod - async def handle_deleted(cls, instance: "Identity"): + def handle_deleted(cls, instance: "Identity"): from activities.models import FanOut if not instance.local: return cls.updated - identity = await instance.afetch_full() - await cls.targets_fan_out(identity, FanOut.Types.identity_deleted) + cls.targets_fan_out(instance, FanOut.Types.identity_deleted) return cls.deleted_fanned_out @classmethod - async def handle_outdated(cls, identity: "Identity"): + def handle_outdated(cls, identity: "Identity"): # Local identities never need fetching if identity.local: return cls.updated # Run the actor fetch and progress to updated if it succeeds - if await identity.fetch_actor(): + if identity.fetch_actor(): return cls.updated @classmethod @@ -365,9 +362,7 @@ class Identity(StatorModel): ) except cls.DoesNotExist: if fetch and not local: - actor_uri, handle = async_to_sync(cls.fetch_webfinger)( - f"{username}@{domain}" - ) + actor_uri, handle = cls.fetch_webfinger(f"{username}@{domain}") if handle is None: return None # See if this actually does match an existing actor @@ -449,14 +444,6 @@ class Identity(StatorModel): def limited(self) -> bool: return self.restriction == self.Restriction.limited - ### Async helpers ### - - async def afetch_full(self): - """ - Returns a version of the object with all relations pre-loaded - """ - return await Identity.objects.select_related("domain").aget(pk=self.pk) - ### ActivityPub (outbound) ### def to_webfinger(self): @@ -637,17 +624,17 @@ class Identity(StatorModel): ### Actor/Webfinger fetching ### @classmethod - async def fetch_webfinger_url(cls, domain: str): + def fetch_webfinger_url(cls, domain: str): """ Given a domain (hostname), returns the correct webfinger URL to use based on probing host-meta. """ - async with httpx.AsyncClient( + with httpx.Client( timeout=settings.SETUP.REMOTE_TIMEOUT, headers={"User-Agent": settings.TAKAHE_USER_AGENT}, ) as client: try: - response = await client.get( + response = client.get( f"https://{domain}/.well-known/host-meta", follow_redirects=True, headers={"Accept": "application/xml"}, @@ -669,24 +656,24 @@ class Identity(StatorModel): return f"https://{domain}/.well-known/webfinger?resource={{uri}}" @classmethod - async def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]: + def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]: """ Given a username@domain handle, returns a tuple of (actor uri, canonical handle) or None, None if it does not resolve. """ domain = handle.split("@")[1].lower() try: - webfinger_url = await cls.fetch_webfinger_url(domain) + webfinger_url = cls.fetch_webfinger_url(domain) except ssl.SSLCertVerificationError: return None, None # Go make a Webfinger request - async with httpx.AsyncClient( + with httpx.Client( timeout=settings.SETUP.REMOTE_TIMEOUT, headers={"User-Agent": settings.TAKAHE_USER_AGENT}, ) as client: try: - response = await client.get( + response = client.get( webfinger_url.format(uri=f"acct:{handle}"), follow_redirects=True, headers={"Accept": "application/json"}, @@ -730,16 +717,16 @@ class Identity(StatorModel): return None, None @classmethod - async def fetch_pinned_post_uris(cls, uri: str) -> list[str]: + def fetch_pinned_post_uris(cls, uri: str) -> list[str]: """ Fetch an identity's featured collection. """ - async with httpx.AsyncClient( + with httpx.Client( timeout=settings.SETUP.REMOTE_TIMEOUT, headers={"User-Agent": settings.TAKAHE_USER_AGENT}, ) as client: try: - response = await client.get( + response = client.get( uri, follow_redirects=True, headers={"Accept": "application/activity+json"}, @@ -785,7 +772,7 @@ class Identity(StatorModel): response.content, ) - async def fetch_actor(self) -> bool: + def fetch_actor(self) -> bool: """ Fetches the user's actor information, as well as their domain from webfinger if it's available. @@ -796,7 +783,7 @@ class Identity(StatorModel): if self.local: raise ValueError("Cannot fetch local identities") try: - response = await SystemActor().signed_request( + response = SystemActor().signed_request( method="get", uri=self.actor_uri, ) @@ -810,7 +797,7 @@ class Identity(StatorModel): if status_code >= 400: if status_code == 410 and self.pk: # Their account got deleted, so let's do the same. - await Identity.objects.filter(pk=self.pk).adelete() + Identity.objects.filter(pk=self.pk).delete() if status_code < 500 and status_code not in [401, 403, 404, 406, 410]: capture_message( @@ -866,44 +853,43 @@ class Identity(StatorModel): ) # Now go do webfinger with that info to see if we can get a canonical domain actor_url_parts = urlparse(self.actor_uri) - get_domain = sync_to_async(Domain.get_remote_domain) if self.username: - webfinger_actor, webfinger_handle = await self.fetch_webfinger( + webfinger_actor, webfinger_handle = self.fetch_webfinger( f"{self.username}@{actor_url_parts.hostname}" ) if webfinger_handle: webfinger_username, webfinger_domain = webfinger_handle.split("@") self.username = webfinger_username - self.domain = await get_domain(webfinger_domain) + self.domain = Domain.get_remote_domain(webfinger_domain) else: - self.domain = await get_domain(actor_url_parts.hostname) + self.domain = Domain.get_remote_domain(actor_url_parts.hostname) else: - self.domain = await get_domain(actor_url_parts.hostname) + self.domain = Domain.get_remote_domain(actor_url_parts.hostname) # Emojis (we need the domain so we do them here) for tag in get_list(document, "tag"): if tag["type"].lower() in ["toot:emoji", "emoji"]: - await sync_to_async(Emoji.by_ap_tag)(self.domain, tag, create=True) + Emoji.by_ap_tag(self.domain, tag, create=True) # Mark as fetched self.fetched = timezone.now() try: - await sync_to_async(self.save)() + self.save() except IntegrityError as e: # See if we can fetch a PK and save there if self.pk is None: try: - other_row = await Identity.objects.aget(actor_uri=self.actor_uri) + other_row = Identity.objects.get(actor_uri=self.actor_uri) except Identity.DoesNotExist: raise ValueError( f"Could not save Identity at end of actor fetch: {e}" ) self.pk: int | None = other_row.pk - await sync_to_async(self.save)() + self.save() # Fetch pinned posts after identity has been fetched and saved if self.featured_collection_uri: - featured = await self.fetch_pinned_post_uris(self.featured_collection_uri) + featured = self.fetch_pinned_post_uris(self.featured_collection_uri) service = IdentityService(self) - await sync_to_async(service.sync_pins)(featured) + service.sync_pins(featured) return True @@ -1016,7 +1002,7 @@ class Identity(StatorModel): ### Cryptography ### - async def signed_request( + def signed_request( self, method: Literal["get", "post"], uri: str, @@ -1025,7 +1011,7 @@ class Identity(StatorModel): """ Performs a signed request on behalf of the System Actor. """ - return await HttpSignature.signed_request( + return HttpSignature.signed_request( method=method, uri=uri, body=body, diff --git a/users/models/report.py b/users/models/report.py index 5a5c27f..b74ed38 100644 --- a/users/models/report.py +++ b/users/models/report.py @@ -2,7 +2,6 @@ from urllib.parse import urlparse import httpx import urlman -from asgiref.sync import sync_to_async from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.db import models @@ -22,26 +21,25 @@ class ReportStates(StateGraph): new.transitions_to(sent) @classmethod - async def handle_new(cls, instance: "Report"): + def handle_new(cls, instance: "Report"): """ Sends the report to the remote server if we need to """ from users.models import SystemActor, User recipients = [] - report = await instance.afetch_full() - async for mod in User.objects.filter( + for mod in User.objects.filter( models.Q(moderator=True) | models.Q(admin=True) ).values_list("email", flat=True): recipients.append(mod) - if report.forward and not report.subject_identity.domain.local: + if instance.forward and not instance.subject_identity.domain.local: system_actor = SystemActor() try: - await system_actor.signed_request( + system_actor.signed_request( method="post", - uri=report.subject_identity.inbox_uri, - body=canonicalise(report.to_ap()), + uri=instance.subject_identity.inbox_uri, + body=canonicalise(instance.to_ap()), ) except httpx.RequestError: pass @@ -50,7 +48,7 @@ class ReportStates(StateGraph): body=render_to_string( "emails/report_new.txt", { - "report": report, + "report": instance, "config": Config.system, "settings": settings, }, @@ -62,14 +60,14 @@ class ReportStates(StateGraph): content=render_to_string( "emails/report_new.html", { - "report": report, + "report": instance, "config": Config.system, "settings": settings, }, ), mimetype="text/html", ) - await sync_to_async(email.send)() + email.send() return cls.sent @@ -145,15 +143,6 @@ class Report(StatorModel): ### ActivityPub ### - async def afetch_full(self) -> "Report": - return await Report.objects.select_related( - "source_identity", - "source_domain", - "subject_identity__domain", - "subject_identity", - "subject_post", - ).aget(pk=self.pk) - @classmethod def handle_ap(cls, data): """ diff --git a/users/models/system_actor.py b/users/models/system_actor.py index 822925c..e942a12 100644 --- a/users/models/system_actor.py +++ b/users/models/system_actor.py @@ -79,7 +79,7 @@ class SystemActor: ], } - async def signed_request( + def signed_request( self, method: Literal["get", "post"], uri: str, @@ -88,7 +88,7 @@ class SystemActor: """ Performs a signed request on behalf of the System Actor. """ - return await HttpSignature.signed_request( + return HttpSignature.signed_request( method=method, uri=uri, body=body, diff --git a/users/views/activitypub.py b/users/views/activitypub.py index 192fd80..cbbd0b0 100644 --- a/users/views/activitypub.py +++ b/users/views/activitypub.py @@ -1,6 +1,5 @@ import json -from asgiref.sync import async_to_sync from django.conf import settings from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse from django.utils.decorators import method_decorator @@ -140,7 +139,7 @@ class Inbox(View): if not identity.public_key: # See if we can fetch it right now - async_to_sync(identity.fetch_actor)() + identity.fetch_actor() if not identity.public_key: exceptions.capture_message(