1
0
Fork 0

Remove opentelemetry-related code from apps.py, formatting

This commit is contained in:
Reinout Meliesie 2025-03-11 02:23:21 +01:00
parent 0702731c7f
commit 64ea602f02
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
2 changed files with 308 additions and 340 deletions

View file

@ -1,97 +1,94 @@
""" access the activity streams stored in redis """
""" Access the activity streams stored in Redis """
from bookwyrm import models
from bookwyrm . redis_store import RedisStore , r
from bookwyrm . tasks import app , STREAMS , IMPORT_TRIGGERED
from datetime import timedelta
from django . dispatch import receiver
from django . db import transaction
from django . db . models import signals , Q
from django . utils import timezone
from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r
from bookwyrm.tasks import app, STREAMS, IMPORT_TRIGGERED
class ActivityStream (RedisStore) :
"""a category of activity stream (like home, local, books)"""
"""A category of activity stream (like home, local, books)"""
def stream_id (self , user_id ) :
"""the redis key for this user's instance of this stream"""
"""The redis key for this user's instance of this stream"""
return f"{user_id}-{self.key}"
def unread_id ( self , user_id ) :
"""the redis key for this user's unread count for this stream"""
"""The redis key for this user's unread count for this stream"""
stream_id = self . stream_id (user_id)
return f"{stream_id}-unread"
def unread_by_status_type_id ( self , user_id ) :
"""the redis key for this user's unread count for this stream"""
"""The redis key for this user's unread count for this stream"""
stream_id = self . stream_id (user_id)
return f"{stream_id}-unread-by-type"
def get_rank ( self , obj ) :
"""statuses are sorted by date published"""
"""Statuses are sorted by date published"""
return obj . published_date . timestamp ()
def add_status ( self , status , increment_unread = False ) :
"""add a status to users' feeds"""
"""Add a status to users' feeds"""
audience = self . get_audience (status)
# the pipeline contains all the add-to-stream activities
# The pipeline contains all the add-to-stream activities
pipeline = self . add_object_to_stores (
status, self.get_stores_for_users(audience), execute=False
status ,
self . get_stores_for_users (audience) ,
execute = False ,
)
if increment_unread :
for user_id in audience :
# add to the unread status count
# Add to the unread status count
pipeline . incr ( self . unread_id (user_id) )
# add to the unread status count for status type
# Add to the unread status count for status type
pipeline . hincrby (
self.unread_by_status_type_id(user_id), get_status_type(status), 1
self . unread_by_status_type_id (user_id) ,
get_status_type (status) ,
1 ,
)
# and go!
# And go!
pipeline . execute ()
def add_user_statuses ( self , viewer , user ) :
"""add a user's statuses to another user's feed"""
# only add the statuses that the viewer should be able to see (ie, not dms)
"""Add a user's statuses to another user's feed"""
# Only add the statuses that the viewer should be able to see (ie, not DMs)
statuses = models . Status . privacy_filter (viewer) . filter ( user = user )
self . bulk_add_objects_to_store ( statuses , self . stream_id ( viewer . id ) )
def remove_user_statuses ( self , viewer , user ) :
"""remove a user's status from another user's feed"""
# remove all so that followers only statuses are removed
"""Remove a user's status from another user's feed"""
# Remove all so that followers only statuses are removed
statuses = user . status_set . all ()
self . bulk_remove_objects_from_store ( statuses , self . stream_id ( viewer . id ) )
def get_activity_stream ( self , user ) :
"""load the statuses to be displayed"""
# clear unreads for this feed
"""Load the statuses to be displayed"""
# Clear unreads for this feed
r . set ( self . unread_id ( user . id ) , 0 )
r . delete ( self . unread_by_status_type_id ( user . id ) )
statuses = self.get_store ( self . stream_id ( user . id ) )
return (
models.Status.objects.select_subclasses()
return models . Status . objects . select_subclasses ()
. filter ( id__in = statuses )
.select_related(
"user",
"reply_parent",
"comment__book",
"review__book",
"quotation__book",
)
. select_related ( "user" , "reply_parent" , "comment__book" , "review__book" , "quotation__book" )
. prefetch_related ( "mention_books" , "mention_users" )
. order_by ("-published_date")
)
def get_unread_count ( self , user ) :
"""get the unread status count for this user's feed"""
"""Get the unread status count for this user's feed"""
return int ( r . get ( self . unread_id ( user . id ) ) or 0 )
def get_unread_count_by_status_type ( self , user ) :
"""get the unread status count for this user's feed's status types"""
"""Get the unread status count for this user's feed's status types"""
status_types = r . hgetall ( self . unread_by_status_type_id ( user . id ) )
return {
str ( key . decode ("utf-8") ) : int (value) or 0
@ -99,59 +96,54 @@ class ActivityStream(RedisStore):
}
def populate_streams ( self , user) :
"""go from zero to a timeline"""
"""Go from zero to a timeline"""
self . populate_store ( self . stream_id ( user . id ) )
def _get_audience(self, status): # pylint: disable=no-self-use
"""given a status, what users should see it, excluding the author"""
# direct messages don't appear in feeds, direct comments/reviews/etc do
def _get_audience ( self , status) :
"""Given a status, what users should see it, excluding the author"""
# Direct messages don't appear in feeds, direct comments/reviews/etc do
if status . privacy == "direct" and status . status_type == "Note" :
return models . User . objects . none ()
# everybody who could plausibly see this status
audience = models.User.objects.filter(
is_active=True,
local=True, # we only create feeds for users of this instance
).exclude(
Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked
# Everybody who could plausibly see this status
audience = models . User . objects
. filter ( is_active = True , local = True ) # We only create feeds for users of this instance
. exclude (
Q ( id__in = status . user . blocks . all () ) |
Q ( blocks = status . user) # Not blocked
)
# only visible to the poster and mentioned users
# Only visible to the poster and mentioned users
if status . privacy == "direct" :
audience = audience.filter(
Q(id__in=status.mention_users.all()) # if the user is mentioned
)
audience = audience . filter ( Q ( id__in = status . mention_users . all () ) ) # If the user is mentioned
# don't show replies to statuses the user can't see
# Don't show replies to statuses the user can't see
elif status . reply_parent and status . reply_parent . privacy == "followers" :
audience = audience . filter (
Q(id=status.reply_parent.user.id) # if the user is the OG author
| (
Q(following=status.user) & Q(following=status.reply_parent.user)
) # if the user is following both authors
Q ( id = status . reply_parent . user . id ) | # If the user is the OG author
( Q ( following = status . user ) & Q ( following = status . reply_parent . user ) ) # If the user is following both authors
)
# only visible to the poster's followers and tagged users
# Only visible to the poster's followers and tagged users
elif status . privacy == "followers" :
audience = audience.filter(
Q(following=status.user) # if the user is following the author
)
audience = audience . filter ( Q ( following = status . user ) ) # If the user is following the author
return audience . distinct ("id")
def get_audience ( self , status ) :
"""given a status, what users should see it"""
"""Given a status, what users should see it"""
audience = self . _get_audience (status) . values_list ( "id" , flat = True )
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
).values_list("id", flat=True)
status_author = models . User . objects
. filter ( is_active = True , local = True , id = status . user . id )
. values_list ( "id" , flat = True )
return list ( set (audience) | set (status_author) )
def get_stores_for_users ( self , user_ids ) :
"""convert a list of user ids into redis store ids"""
"""Convert a list of user ids into Redis store ids"""
return [ self . stream_id (user_id) for user_id in user_ids ]
def get_statuses_for_user(self, user): # pylint: disable=no-self-use
"""given a user, what statuses should they see on this stream"""
def get_statuses_for_user ( self , user ) :
"""Given a user, what statuses should they see on this stream"""
return models . Status . privacy_filter (
user ,
privacy_levels = [ "public" , "unlisted" , "followers" ] ,
@ -163,104 +155,91 @@ class ActivityStream(RedisStore):
class HomeStream (ActivityStream) :
"""users you follow"""
"""Users you follow"""
key = "home"
def get_audience ( self , status ) :
audience = super () . _get_audience (status)
# if the user is following the author
# If the user is following the author
audience = audience . filter ( following = status . user ) . values_list ( "id" , flat = True )
# if the user is the post's author
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
).values_list("id", flat=True)
# If the user is the post's author
status_author = models . User . objects
. filter ( is_active = True , local = True , id = status . user . id )
. values_list ( "id" , flat = True )
return list ( set (audience) | set (status_author) )
def get_statuses_for_user ( self , user ) :
return models . Status . privacy_filter (
user ,
privacy_levels = [ "public" , "unlisted" , "followers" ] ,
).exclude(
~Q( # remove everything except
Q(user__followers=user) # user following
| Q(user=user) # is self
| Q(mention_users=user) # mentions user
),
)
) . exclude ( ~ Q ( # Remove everything except
Q ( user__followers = user ) | # User following
Q ( user = user ) | # Is self
Q ( mention_users = user ) # Mentions user
) )
class LocalStream (ActivityStream) :
"""users you follow"""
"""Users you follow"""
key = "local"
def get_audience ( self , status ) :
# this stream wants no part in non-public statuses
# This stream wants no part in non-public statuses
if status . privacy != "public" or not status . user . local :
return []
return super () . get_audience (status)
def get_statuses_for_user ( self , user ) :
# all public statuses by a local user
return models.Status.privacy_filter(
user,
privacy_levels=["public"],
).filter(user__local=True)
# All public statuses by a local user
return models . Status
. privacy_filter ( user , privacy_levels = [ "public" ] )
. filter ( user__local = True )
class BooksStream (ActivityStream) :
"""books on your shelves"""
"""Books on your shelves"""
key = "books"
def _get_audience ( self , status ) :
"""anyone with the mentioned book on their shelves"""
work = (
status.book.parent_work
if hasattr(status, "book")
"""Anyone with the mentioned book on their shelves"""
work = status . book . parent_work if hasattr ( status , "book" )
else status . mention_books . first () . parent_work
)
audience = super () . _get_audience (status)
return audience . filter ( shelfbook__book__parent_work = work )
def get_audience ( self , status ) :
# only show public statuses on the books feed,
# and only statuses that mention books
if status.privacy != "public" or not (
status.mention_books.exists() or hasattr(status, "book")
# Only show public statuses on the books feed, and only statuses that mention books
if (
status . privacy != "public" or
not ( status . mention_books . exists () or hasattr ( status , "book" ) )
) :
return []
return super () . get_audience (status)
def get_statuses_for_user ( self , user ) :
"""any public status that mentions the user's books"""
books = user.shelfbook_set.values_list(
"book__parent_work__id", flat=True
).distinct()
return (
models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
"""Any public status that mentions the user's books"""
books = user . shelfbook_set
. values_list ( "book__parent_work__id" , flat = True )
. distinct ()
return models . Status
. privacy_filter ( user , privacy_levels = [ "public" ] )
. filter (
Q(comment__book__parent_work__id__in=books)
| Q(quotation__book__parent_work__id__in=books)
| Q(review__book__parent_work__id__in=books)
| Q(mention_books__parent_work__id__in=books)
Q ( comment__book__parent_work__id__in = books ) |
Q ( quotation__book__parent_work__id__in = books ) |
Q ( review__book__parent_work__id__in = books ) |
Q ( mention_books__parent_work__id__in = books )
)
. distinct ()
)
def add_book_statuses ( self , user , book ) :
"""add statuses about a book to a user's feed"""
"""Add statuses about a book to a user's feed"""
work = book . parent_work
statuses = models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
book_comments = statuses . filter ( Q ( comment__book__parent_work = work ) )
book_quotations = statuses . filter ( Q ( quotation__book__parent_work = work ) )
@ -273,12 +252,9 @@ class BooksStream(ActivityStream):
self . bulk_add_objects_to_store ( book_mentions , self . stream_id ( user . id ) )
def remove_book_statuses ( self , user , book ) :
"""add statuses about a book to a user's feed"""
"""Add statuses about a book to a user's feed"""
work = book . parent_work
statuses = models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
book_comments = statuses . filter ( Q ( comment__book__parent_work = work ) )
book_quotations = statuses . filter ( Q ( quotation__book__parent_work = work ) )
@ -291,7 +267,7 @@ class BooksStream(ActivityStream):
self . bulk_remove_objects_from_store ( book_mentions , self . stream_id ( user . id ) )
# determine which streams are enabled in settings.py
# Determine which streams are enabled in settings.py
streams = {
"home" : HomeStream () ,
"local" : LocalStream () ,
@ -300,10 +276,9 @@ streams = {
@ receiver ( signals . post_save )
# pylint: disable=unused-argument
def add_status_on_create ( sender , instance , created , * args , ** kwargs ) :
"""add newly created statuses to activity feeds"""
# we're only interested in new statuses
"""Add newly created statuses to activity feeds"""
# We're only interested in new statuses
if not issubclass ( sender , models . Status ) :
return
@ -311,33 +286,32 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
remove_status_task . delay ( instance . id )
return
# We don't want to create multiple add_status_tasks for each status, and because
# the transactions are atomic, on_commit won't run until the status is ready to add.
# We don't want to create multiple add_status_tasks for each status, and because the transactions are atomic,
# on_commit won't run until the status is ready to add.
if not created :
return
# when creating new things, gotta wait on the transaction
transaction.on_commit(
lambda: add_status_on_create_command(sender, instance, created)
)
# When creating new things, gotta wait on the transaction
transaction . on_commit ( lambda : add_status_on_create_command ( sender , instance , created ) )
def add_status_on_create_command ( sender , instance , created ) :
"""runs this code only after the database commit completes"""
# boosts trigger 'saves" twice, so don't bother duplicating the task
"""Runs this code only after the database commit completes"""
# Boosts trigger 'saves" twice, so don't bother duplicating the task
if sender == models . Boost and not created :
return
priority = STREAMS
# check if this is an old status, de-prioritize if so
# Check if this is an old status, de-prioritize if so
# (this will happen if federation is very slow, or, more expectedly, on csv import)
if instance.published_date < timezone.now() - timedelta(
days=1
) or instance.created_date < instance.published_date - timedelta(days=1):
# a backdated status from a local user is an import, don't add it
if (
instance . published_date < timezone . now () - timedelta ( days = 1 ) or
instance.created_date < instance.published_date - timedelta(days=1)
) :
# A backdated status from a local user is an import, don't add it
if instance . user . local :
return
# an out of date remote status is a low priority but should be added
# An out of date remote status is a low priority but should be added
priority = IMPORT_TRIGGERED
add_status_task . apply_async (
@ -351,56 +325,57 @@ def add_status_on_create_command(sender, instance, created):
@ receiver ( signals . post_delete , sender = models . Boost )
# pylint: disable=unused-argument
def remove_boost_on_delete ( sender , instance , * args , ** kwargs ) :
"""boosts are deleted"""
# remove the boost
"""Boosts are deleted"""
# Remove the boost
remove_status_task . delay ( instance . id )
# re-add the original status
# Re-add the original status
add_status_task . delay ( instance . boosted_status . id )
@ receiver ( signals . post_save , sender = models . UserFollows )
# pylint: disable=unused-argument
def add_statuses_on_follow ( sender , instance , created , * args , ** kwargs ) :
"""add a newly followed user's statuses to feeds"""
"""Add a newly followed user's statuses to feeds"""
if not created or not instance . user_subject . local :
return
add_user_statuses_task . delay (
instance.user_subject.id, instance.user_object.id, stream_list=["home"]
instance . user_subject . id ,
instance . user_object . id ,
stream_list = [ "home" ] ,
)
@ receiver ( signals . post_delete , sender = models . UserFollows )
# pylint: disable=unused-argument
def remove_statuses_on_unfollow ( sender , instance , * args , ** kwargs ) :
"""remove statuses from a feed on unfollow"""
"""Remove statuses from a feed on unfollow"""
if not instance . user_subject . local :
return
remove_user_statuses_task . delay (
instance.user_subject.id, instance.user_object.id, stream_list=["home"]
instance . user_subject . id ,
instance . user_object . id ,
stream_list = [ "home" ] ,
)
@ receiver ( signals . post_save , sender = models . UserBlocks )
# pylint: disable=unused-argument
def remove_statuses_on_block ( sender , instance , * args , ** kwargs ) :
"""remove statuses from all feeds on block"""
# blocks apply ot all feeds
"""Remove statuses from all feeds on block"""
# Blocks apply ot all feeds
if instance . user_subject . local :
remove_user_statuses_task . delay (
instance.user_subject.id, instance.user_object.id
instance . user_subject . id ,
instance . user_object . id ,
)
# and in both directions
# And in both directions
if instance . user_object . local :
remove_user_statuses_task . delay (
instance.user_object.id, instance.user_subject.id
instance . user_object . id ,
instance . user_subject . id ,
)
@receiver(signals.post_delete, sender=models.UserBlocks)
# pylint: disable=unused-argument
def add_statuses_on_unblock(sender, instance, *args, **kwargs):
"""add statuses back to all feeds on unblock"""
# make sure there isn't a block in the other direction
@ -430,7 +405,6 @@ def add_statuses_on_unblock(sender, instance, *args, **kwargs):
@receiver(signals.post_save, sender=models.User)
# pylint: disable=unused-argument
def populate_streams_on_account_create(sender, instance, created, *args, **kwargs):
"""build a user's feeds when they join"""
if not created or not instance.local:
@ -447,7 +421,6 @@ def populate_streams_on_account_create_command(instance_id):
@receiver(signals.pre_save, sender=models.ShelfBook)
# pylint: disable=unused-argument
def add_statuses_on_shelve(sender, instance, *args, **kwargs):
"""update books stream when user shelves a book"""
if not instance.user.local:
@ -463,7 +436,6 @@ def add_statuses_on_shelve(sender, instance, *args, **kwargs):
@receiver(signals.post_delete, sender=models.ShelfBook)
# pylint: disable=unused-argument
def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
"""update books stream when user unshelves a book"""
if not instance.user.local:

View file

@ -1,12 +1,15 @@
"""Do further startup configuration and initialization"""
import logging
import os
import urllib
import logging
from django.apps import AppConfig
from bookwyrm import settings
from django . apps import AppConfig
logger = logging . getLogger (__name__)
@ -23,7 +26,7 @@ def download_file(url, destination):
logger . error ( "Failed to download file %s: %s" , url , err )
except OSError as err :
logger . error ( "Couldn't open font file %s for writing: %s" , destination , err )
except Exception as err: # pylint:disable=broad-except
except Exception as err :
logger . error ( "Unknown error in file download: %s" , err )
@ -34,14 +37,7 @@ class BookwyrmConfig(AppConfig):
verbose_name = "BookWyrm"
def ready (self) :
"""set up OTLP and preview image files, if desired"""
if settings.OTEL_EXPORTER_OTLP_ENDPOINT or settings.OTEL_EXPORTER_CONSOLE:
# pylint: disable=import-outside-toplevel
from bookwyrm.telemetry import open_telemetry
open_telemetry.instrumentDjango()
open_telemetry.instrumentPostgres()
"""Set up preview image files, if desired"""
if settings . ENABLE_PREVIEW_IMAGES and settings . FONTS :
# Download any fonts that we don't have yet
logger . debug ("Downloading fonts..")