Switch to SQLAlchemy 2.0 query style

This commit is contained in:
Thomas Sileo 2022-06-29 08:56:39 +02:00
parent f4c70096e2
commit 18bd2cb664
8 changed files with 266 additions and 207 deletions

View file

@ -3,6 +3,7 @@ from dataclasses import dataclass
from typing import Union
from urllib.parse import urlparse
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
@ -151,9 +152,9 @@ def save_actor(db: Session, ap_actor: ap.RawObject) -> "ActorModel":
def fetch_actor(db: Session, actor_id: str) -> "ActorModel":
from app import models
existing_actor = (
db.query(models.Actor).filter(models.Actor.ap_id == actor_id).one_or_none()
)
existing_actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == actor_id)
).scalar_one_or_none()
if existing_actor:
return existing_actor
@ -183,27 +184,30 @@ def get_actors_metadata(
ap_actor_ids = [actor.ap_id for actor in actors]
followers = {
follower.ap_actor_id: follower.inbox_object.ap_id
for follower in db.query(models.Follower)
.filter(models.Follower.ap_actor_id.in_(ap_actor_ids))
.options(joinedload(models.Follower.inbox_object))
for follower in db.scalars(
select(models.Follower)
.where(models.Follower.ap_actor_id.in_(ap_actor_ids))
.options(joinedload(models.Follower.inbox_object))
)
.unique()
.all()
}
following = {
following.ap_actor_id
for following in db.query(models.Following.ap_actor_id)
.filter(models.Following.ap_actor_id.in_(ap_actor_ids))
.all()
for following in db.execute(
select(models.Following.ap_actor_id).where(
models.Following.ap_actor_id.in_(ap_actor_ids)
)
)
}
sent_follow_requests = {
follow_req.ap_object["object"]: follow_req.ap_id
for follow_req in db.query(
models.OutboxObject.ap_object, models.OutboxObject.ap_id
for follow_req in db.execute(
select(models.OutboxObject.ap_object, models.OutboxObject.ap_id).where(
models.OutboxObject.ap_type == "Follow",
models.OutboxObject.undone_by_outbox_object_id.is_(None),
)
)
.filter(
models.OutboxObject.ap_type == "Follow",
models.OutboxObject.undone_by_outbox_object_id.is_(None),
)
.all()
}
idx: ActorsMetadata = {}
for actor in actors:

View file

@ -6,6 +6,8 @@ from fastapi import Request
from fastapi import UploadFile
from fastapi.exceptions import HTTPException
from fastapi.responses import RedirectResponse
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
@ -141,16 +143,20 @@ def admin_bookmarks(
db: Session = Depends(get_db),
) -> templates.TemplateResponse:
stream = (
db.query(models.InboxObject)
.filter(
models.InboxObject.ap_type.in_(["Note", "Article", "Video", "Announce"]),
models.InboxObject.is_hidden_from_stream.is_(False),
models.InboxObject.undone_by_inbox_object_id.is_(None),
models.InboxObject.is_bookmarked.is_(True),
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
.all()
db.scalars(
select(models.InboxObject)
.where(
models.InboxObject.ap_type.in_(
["Note", "Article", "Video", "Announce"]
),
models.InboxObject.is_hidden_from_stream.is_(False),
models.InboxObject.undone_by_inbox_object_id.is_(None),
models.InboxObject.is_bookmarked.is_(True),
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
).all()
# TODO: joinedload + unique
)
return templates.render_template(
db,
@ -169,27 +175,28 @@ def admin_inbox(
filter_by: str | None = None,
cursor: str | None = None,
) -> templates.TemplateResponse:
q = db.query(models.InboxObject).filter(
models.InboxObject.ap_type.not_in(["Accept"])
)
where = [models.InboxObject.ap_type.not_in(["Accept"])]
if filter_by:
q = q.filter(models.InboxObject.ap_type == filter_by)
where.append(models.InboxObject.ap_type == filter_by)
if cursor:
q = q.filter(
where.append(
models.InboxObject.ap_published_at < pagination.decode_cursor(cursor)
)
page_size = 20
remaining_count = q.count()
remaining_count = db.scalar(select(func.count(models.InboxObject.id)).where(*where))
q = select(models.InboxObject).where(*where)
inbox = (
q.options(
joinedload(models.InboxObject.relates_to_inbox_object),
joinedload(models.InboxObject.relates_to_outbox_object),
db.scalars(
q.options(
joinedload(models.InboxObject.relates_to_inbox_object),
joinedload(models.InboxObject.relates_to_outbox_object),
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
.unique()
.all()
)
@ -227,27 +234,31 @@ def admin_outbox(
filter_by: str | None = None,
cursor: str | None = None,
) -> templates.TemplateResponse:
q = db.query(models.OutboxObject).filter(
models.OutboxObject.ap_type.not_in(["Accept"])
)
where = [models.OutboxObject.ap_type.not_in(["Accept"])]
if filter_by:
q = q.filter(models.OutboxObject.ap_type == filter_by)
where.append(models.OutboxObject.ap_type == filter_by)
if cursor:
q = q.filter(
where.append(
models.OutboxObject.ap_published_at < pagination.decode_cursor(cursor)
)
page_size = 20
remaining_count = q.count()
remaining_count = db.scalar(
select(func.count(models.OutboxObject.id)).where(*where)
)
q = select(models.OutboxObject).where(*where)
outbox = (
q.options(
joinedload(models.OutboxObject.relates_to_inbox_object),
joinedload(models.OutboxObject.relates_to_outbox_object),
joinedload(models.OutboxObject.relates_to_actor),
db.scalars(
q.options(
joinedload(models.OutboxObject.relates_to_inbox_object),
joinedload(models.OutboxObject.relates_to_outbox_object),
joinedload(models.OutboxObject.relates_to_actor),
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(page_size)
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(page_size)
.unique()
.all()
)
@ -283,13 +294,16 @@ def get_notifications(
request: Request, db: Session = Depends(get_db)
) -> templates.TemplateResponse:
notifications = (
db.query(models.Notification)
.options(
joinedload(models.Notification.actor),
joinedload(models.Notification.inbox_object),
joinedload(models.Notification.outbox_object),
db.scalars(
select(models.Notification)
.options(
joinedload(models.Notification.actor),
joinedload(models.Notification.inbox_object),
joinedload(models.Notification.outbox_object),
)
.order_by(models.Notification.created_at.desc())
)
.order_by(models.Notification.created_at.desc())
.unique()
.all()
)
actors_metadata = get_actors_metadata(
@ -337,21 +351,22 @@ def admin_profile(
actor_id: str,
db: Session = Depends(get_db),
) -> templates.TemplateResponse:
actor = db.query(models.Actor).filter(models.Actor.ap_id == actor_id).one_or_none()
actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == actor_id)
).scalar_one_or_none()
if not actor:
raise HTTPException(status_code=404)
actors_metadata = get_actors_metadata(db, [actor])
inbox_objects = (
db.query(models.InboxObject)
.filter(
inbox_objects = db.scalars(
select(models.InboxObject)
.where(
models.InboxObject.actor_id == actor.id,
models.InboxObject.ap_type.in_(["Note", "Article", "Video"]),
)
.order_by(models.InboxObject.ap_published_at.desc())
.all()
)
).all()
return templates.render_template(
db,

View file

@ -7,6 +7,10 @@ from urllib.parse import urlparse
import httpx
from dateutil.parser import isoparse
from loguru import logger
from sqlalchemy import delete
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy import update
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
@ -189,9 +193,11 @@ def send_undo(db: Session, ap_object_id: str) -> None:
outbox_object.id,
)
# Also remove the follow from the following collection
db.query(models.Following).filter(
models.Following.ap_actor_id == followed_actor.ap_id
).delete()
db.execute(
delete(models.Following).where(
models.Following.ap_actor_id == followed_actor.ap_id
)
)
db.commit()
elif outbox_object_to_undo.ap_type == "Like":
liked_object_ap_id = outbox_object_to_undo.activity_object_ap_id
@ -249,9 +255,13 @@ def send_create(
context = in_reply_to_object.ap_context
if in_reply_to_object.is_from_outbox:
db.query(models.OutboxObject).filter(
models.OutboxObject.ap_id == in_reply_to,
).update({"replies_count": models.OutboxObject.replies_count + 1})
db.execute(
update(models.OutboxObject)
.where(
models.OutboxObject.ap_id == in_reply_to,
)
.values(replies_count=models.OutboxObject.replies_count + 1)
)
for (upload, filename) in uploads:
attachments.append(upload_to_attachment(upload, filename))
@ -339,9 +349,9 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
continue
# Is it a known actor?
known_actor = (
db.query(models.Actor).filter(models.Actor.ap_id == r).one_or_none()
)
known_actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == r)
).scalar_one_or_none()
if known_actor:
recipients.add(known_actor.shared_inbox_url or known_actor.inbox_url)
continue
@ -361,19 +371,15 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
def get_inbox_object_by_ap_id(db: Session, ap_id: str) -> models.InboxObject | None:
return (
db.query(models.InboxObject)
.filter(models.InboxObject.ap_id == ap_id)
.one_or_none()
)
return db.execute(
select(models.InboxObject).where(models.InboxObject.ap_id == ap_id)
).scalar_one_or_none()
def get_outbox_object_by_ap_id(db: Session, ap_id: str) -> models.OutboxObject | None:
return (
db.query(models.OutboxObject)
.filter(models.OutboxObject.ap_id == ap_id)
.one_or_none()
)
return db.execute(
select(models.OutboxObject).where(models.OutboxObject.ap_id == ap_id)
).scalar_one_or_none()
def get_anybox_object_by_ap_id(db: Session, ap_id: str) -> AnyboxObject | None:
@ -456,9 +462,11 @@ def _handle_undo_activity(
if ap_activity_to_undo.ap_type == "Follow":
logger.info(f"Undo follow from {from_actor.ap_id}")
db.query(models.Follower).filter(
models.Follower.inbox_object_id == ap_activity_to_undo.id
).delete()
db.execute(
delete(models.Follower).where(
models.Follower.inbox_object_id == ap_activity_to_undo.id
)
)
notif = models.Notification(
notification_type=models.NotificationType.UNFOLLOW,
actor_id=from_actor.id,
@ -536,9 +544,13 @@ def _handle_create_activity(
return None
if created_object.in_reply_to and created_object.in_reply_to.startswith(BASE_URL):
db.query(models.OutboxObject).filter(
models.OutboxObject.ap_id == created_object.in_reply_to,
).update({"replies_count": models.OutboxObject.replies_count + 1})
db.execute(
update(models.OutboxObject)
.where(
models.OutboxObject.ap_id == created_object.in_reply_to,
)
.values(replies_count=models.OutboxObject.replies_count + 1)
)
for tag in tags:
if tag.get("name") == LOCAL_ACTOR.handle or tag.get("href") == LOCAL_ACTOR.url:
@ -564,9 +576,11 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
ra = RemoteObject(ap.unwrap_activity(raw_object), actor=actor)
if (
db.query(models.InboxObject)
.filter(models.InboxObject.ap_id == ra.ap_id)
.count()
db.scalar(
select(func.count(models.InboxObject.id)).where(
models.InboxObject.ap_id == ra.ap_id
)
)
> 0
):
logger.info(f"Received duplicate {ra.ap_type} activity: {ra.ap_id}")
@ -759,21 +773,25 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
def public_outbox_objects_count(db: Session) -> int:
return (
db.query(models.OutboxObject)
.filter(
return db.scalar(
select(func.count(models.OutboxObject.id)).where(
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
)
.count()
)
def fetch_actor_collection(db: Session, url: str) -> list[Actor]:
if url.startswith(config.BASE_URL):
if url == config.BASE_URL + "/followers":
q = db.query(models.Follower).options(joinedload(models.Follower.actor))
return [follower.actor for follower in q.all()]
followers = (
db.scalars(
select(models.Follower).options(joinedload(models.Follower.actor))
)
.unique()
.all()
)
return [follower.actor for follower in followers]
else:
raise ValueError(f"internal collection for {url}) not supported")
@ -795,19 +813,19 @@ def get_replies_tree(
# TODO: handle visibility
tree_nodes: list[AnyboxObject] = []
tree_nodes.extend(
db.query(models.InboxObject)
.filter(
models.InboxObject.ap_context == requested_object.ap_context,
)
.all()
db.scalars(
select(models.InboxObject).where(
models.InboxObject.ap_context == requested_object.ap_context,
)
).all()
)
tree_nodes.extend(
db.query(models.OutboxObject)
.filter(
models.OutboxObject.ap_context == requested_object.ap_context,
models.OutboxObject.is_deleted.is_(False),
)
.all()
db.scalars(
select(models.OutboxObject).where(
models.OutboxObject.ap_context == requested_object.ap_context,
models.OutboxObject.is_deleted.is_(False),
)
).all()
)
nodes_by_in_reply_to = defaultdict(list)
for node in tree_nodes:

View file

@ -22,6 +22,8 @@ from fastapi.staticfiles import StaticFiles
from feedgen.feed import FeedGenerator # type: ignore
from loguru import logger
from PIL import Image
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
from starlette.background import BackgroundTask
@ -147,24 +149,28 @@ def index(
return ActivityPubResponse(LOCAL_ACTOR.ap_actor)
page = page or 1
q = db.query(models.OutboxObject).filter(
where = (
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
models.OutboxObject.is_hidden_from_homepage.is_(False),
)
total_count = q.count()
q = select(models.OutboxObject).where(*where)
total_count = db.scalar(select(func.count(models.OutboxObject.id)).where(*where))
page_size = 20
page_offset = (page - 1) * page_size
outbox_objects = (
q.options(
joinedload(models.OutboxObject.outbox_object_attachments).options(
joinedload(models.OutboxObjectAttachment.upload)
db.scalars(
q.options(
joinedload(models.OutboxObject.outbox_object_attachments).options(
joinedload(models.OutboxObjectAttachment.upload)
)
)
.order_by(models.OutboxObject.ap_published_at.desc())
.offset(page_offset)
.limit(page_size)
)
.order_by(models.OutboxObject.ap_published_at.desc())
.offset(page_offset)
.limit(page_size)
.unique()
.all()
)
@ -200,20 +206,22 @@ def _build_followx_collection(
"totalItems": total_items,
}
q = db.query(model_cls).order_by(model_cls.created_at.desc()) # type: ignore
q = select(model_cls).order_by(model_cls.created_at.desc()) # type: ignore
if next_cursor:
q = q.filter(
q = q.where(
model_cls.created_at < pagination.decode_cursor(next_cursor) # type: ignore
)
q = q.limit(20)
items = [followx for followx in q.all()]
items = [followx for followx in db.scalars(q).all()]
next_cursor = None
if (
items
and db.query(model_cls)
.filter(model_cls.created_at < items[-1].created_at)
.count()
and db.scalar(
select(func.count(model_cls.id)).where(
model_cls.created_at < items[-1].created_at
)
)
> 0
):
next_cursor = pagination.encode_cursor(items[-1].created_at)
@ -257,10 +265,13 @@ def followers(
# We only show the most recent 20 followers on the public website
followers = (
db.query(models.Follower)
.options(joinedload(models.Follower.actor))
.order_by(models.Follower.created_at.desc())
.limit(20)
db.scalars(
select(models.Follower)
.options(joinedload(models.Follower.actor))
.order_by(models.Follower.created_at.desc())
.limit(20)
)
.unique()
.all()
)
@ -303,13 +314,15 @@ def following(
)
# We only show the most recent 20 follows on the public website
q = (
db.query(models.Following)
.options(joinedload(models.Following.actor))
.order_by(models.Following.created_at.desc())
.limit(20)
following = (
db.scalars(
select(models.Following)
.options(joinedload(models.Following.actor))
.order_by(models.Following.created_at.desc())
)
.unique()
.all()
)
following = q.all()
# TODO: support next_cursor/prev_cursor
actors_metadata = {}
@ -336,16 +349,15 @@ def outbox(
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
) -> ActivityPubResponse:
# By design, we only show the last 20 public activities in the oubox
outbox_objects = (
db.query(models.OutboxObject)
.filter(
outbox_objects = db.scalars(
select(models.OutboxObject)
.where(
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(20)
.all()
)
).all()
return ActivityPubResponse(
{
"@context": ap.AS_EXTENDED_CTX,
@ -365,8 +377,8 @@ def featured(
db: Session = Depends(get_db),
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
) -> ActivityPubResponse:
outbox_objects = (
db.query(models.OutboxObject)
outbox_objects = db.scalars(
select(models.OutboxObject)
.filter(
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
@ -374,8 +386,7 @@ def featured(
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(5)
.all()
)
).all()
return ActivityPubResponse(
{
"@context": ap.AS_EXTENDED_CTX,
@ -421,17 +432,20 @@ def outbox_by_public_id(
httpsig_info: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
) -> ActivityPubResponse | templates.TemplateResponse:
maybe_object = (
db.query(models.OutboxObject)
.options(
joinedload(models.OutboxObject.outbox_object_attachments).options(
joinedload(models.OutboxObjectAttachment.upload)
db.execute(
select(models.OutboxObject)
.options(
joinedload(models.OutboxObject.outbox_object_attachments).options(
joinedload(models.OutboxObjectAttachment.upload)
)
)
.where(
models.OutboxObject.public_id == public_id,
models.OutboxObject.is_deleted.is_(False),
)
)
.filter(
models.OutboxObject.public_id == public_id,
models.OutboxObject.is_deleted.is_(False),
)
.one_or_none()
.unique()
.scalar_one_or_none()
)
if not maybe_object:
raise HTTPException(status_code=404)
@ -444,25 +458,33 @@ def outbox_by_public_id(
replies_tree = boxes.get_replies_tree(db, maybe_object)
likes = (
db.query(models.InboxObject)
.filter(
models.InboxObject.ap_type == "Like",
models.InboxObject.activity_object_ap_id == maybe_object.ap_id,
db.scalars(
select(models.InboxObject)
.where(
models.InboxObject.ap_type == "Like",
models.InboxObject.activity_object_ap_id == maybe_object.ap_id,
)
.options(joinedload(models.InboxObject.actor))
.order_by(models.InboxObject.ap_published_at.desc())
.limit(10)
)
.options(joinedload(models.InboxObject.actor))
.order_by(models.InboxObject.ap_published_at.desc())
.limit(10)
.unique()
.all()
)
shares = (
db.query(models.InboxObject)
.filter(
models.InboxObject.ap_type == "Announce",
models.InboxObject.activity_object_ap_id == maybe_object.ap_id,
db.scalars(
select(models.InboxObject)
.filter(
models.InboxObject.ap_type == "Announce",
models.InboxObject.activity_object_ap_id == maybe_object.ap_id,
)
.options(joinedload(models.InboxObject.actor))
.order_by(models.InboxObject.ap_published_at.desc())
.limit(10)
)
.options(joinedload(models.InboxObject.actor))
.order_by(models.InboxObject.ap_published_at.desc())
.limit(10)
.unique()
.all()
)
return templates.render_template(
@ -485,14 +507,12 @@ def outbox_activity_by_public_id(
db: Session = Depends(get_db),
httpsig_info: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
) -> ActivityPubResponse:
maybe_object = (
db.query(models.OutboxObject)
.filter(
maybe_object = db.execute(
select(models.OutboxObject).where(
models.OutboxObject.public_id == public_id,
models.OutboxObject.is_deleted.is_(False),
)
.one_or_none()
)
).scalar_one_or_none()
if not maybe_object:
raise HTTPException(status_code=404)
@ -765,13 +785,11 @@ def serve_attachment(
filename: str,
db: Session = Depends(get_db),
):
upload = (
db.query(models.Upload)
.filter(
upload = db.execute(
select(models.Upload).where(
models.Upload.content_hash == content_hash,
)
.one_or_none()
)
).scalar_one_or_none()
if not upload:
raise HTTPException(status_code=404)
@ -787,13 +805,11 @@ def serve_attachment_thumbnail(
filename: str,
db: Session = Depends(get_db),
):
upload = (
db.query(models.Upload)
.filter(
upload = db.execute(
select(models.Upload).where(
models.Upload.content_hash == content_hash,
)
.one_or_none()
)
).scalar_one_or_none()
if not upload or not upload.has_thumbnail:
raise HTTPException(status_code=404)
@ -812,17 +828,16 @@ Disallow: /admin"""
def _get_outbox_for_feed(db: Session) -> list[models.OutboxObject]:
return (
db.query(models.OutboxObject)
.filter(
return db.scalars(
select(models.OutboxObject)
.where(
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
models.OutboxObject.ap_type.in_(["Note", "Article", "Video"]),
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(20)
.all()
)
).all()
@app.get("/feed.json")

View file

@ -6,6 +6,8 @@ from datetime import timedelta
import httpx
from loguru import logger
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from app import activitypub as ap
@ -67,22 +69,23 @@ def _set_next_try(
def process_next_outgoing_activity(db: Session) -> bool:
q = (
db.query(models.OutgoingActivity)
.filter(
models.OutgoingActivity.next_try <= now(),
models.OutgoingActivity.is_errored.is_(False),
models.OutgoingActivity.is_sent.is_(False),
)
.order_by(models.OutgoingActivity.next_try)
)
q_count = q.count()
where = [
models.OutgoingActivity.next_try <= now(),
models.OutgoingActivity.is_errored.is_(False),
models.OutgoingActivity.is_sent.is_(False),
]
q_count = db.scalar(select(func.count(models.OutgoingActivity.id)).where(*where))
logger.info(f"{q_count} outgoing activities ready to process")
if not q_count:
logger.info("No activities to process")
return False
next_activity = q.limit(1).one()
next_activity = db.execute(
select(models.OutgoingActivity)
.where(*where)
.limit(1)
.order_by(models.OutgoingActivity.next_try)
).scalar_one()
next_activity.tries = next_activity.tries + 1
next_activity.last_try = now()

View file

@ -1,6 +1,7 @@
import re
from markdown import markdown
from sqlalchemy import select
from sqlalchemy.orm import Session
from app import models
@ -43,9 +44,9 @@ def _mentionify(
mentioned_actors = []
for mention in re.findall(_MENTION_REGEX, content):
_, username, domain = mention.split("@")
actor = (
db.query(models.Actor).filter(models.Actor.handle == mention).one_or_none()
)
actor = db.execute(
select(models.Actor).where(models.Actor.handle == mention)
).scalar_one_or_none()
if not actor:
actor_url = webfinger.get_actor_url(mention)
if not actor_url:

View file

@ -13,6 +13,8 @@ from bs4 import BeautifulSoup # type: ignore
from fastapi import Request
from fastapi.templating import Jinja2Templates
from loguru import logger
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from starlette.templating import _TemplateResponse as TemplateResponse
@ -94,14 +96,16 @@ def render_template(
"csrf_token": generate_csrf_token() if is_admin else None,
"highlight_css": HIGHLIGHT_CSS,
"visibility_enum": ap.VisibilityEnum,
"notifications_count": db.query(models.Notification)
.filter(models.Notification.is_new.is_(True))
.count()
"notifications_count": db.scalar(
select(func.count(models.Notification.id)).where(
models.Notification.is_new.is_(True)
)
)
if is_admin
else 0,
"local_actor": LOCAL_ACTOR,
"followers_count": db.query(models.Follower).count(),
"following_count": db.query(models.Following).count(),
"followers_count": db.scalar(select(func.count(models.Follower.id))),
"following_count": db.scalar(select(func.count(models.Following.id))),
**template_args,
},
)

View file

@ -5,6 +5,7 @@ import blurhash # type: ignore
from fastapi import UploadFile
from loguru import logger
from PIL import Image
from sqlalchemy import select
from app import activitypub as ap
from app import models
@ -27,11 +28,9 @@ def save_upload(db: Session, f: UploadFile) -> models.Upload:
content_hash = h.hexdigest()
f.file.seek(0)
existing_upload = (
db.query(models.Upload)
.filter(models.Upload.content_hash == content_hash)
.one_or_none()
)
existing_upload = db.execute(
select(models.Upload).where(models.Upload.content_hash == content_hash)
).scalar_one_or_none()
if existing_upload:
logger.info(f"Upload with {content_hash=} already exists")
return existing_upload