diff --git a/Makefile b/Makefile index 774fe32..1384e16 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,11 @@ tasks: test: uv run pytest -n auto +lint: + uv run ruff check bookmarks + format: - uv run black bookmarks + uv run ruff format bookmarks uv run djlint bookmarks/templates --reformat --quiet --warn npx prettier bookmarks/frontend --write npx prettier bookmarks/styles --write diff --git a/README.md b/README.md index 6393571..89e357f 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,17 @@ Run all tests with pytest: make test ``` + +### Linting + +Run linting with ruff: +``` +make lint +``` + ### Formatting -Format Python code with black, Django templates with djlint, and JavaScript code with prettier: +Format Python code with ruff, Django templates with djlint, and JavaScript code with prettier: ``` make format ``` diff --git a/bookmarks/admin.py b/bookmarks/admin.py index 7317939..01776a6 100644 --- a/bookmarks/admin.py +++ b/bookmarks/admin.py @@ -1,4 +1,5 @@ import os + from django import forms from django.contrib import admin, messages from django.contrib.admin import AdminSite @@ -8,7 +9,7 @@ from django.core.paginator import Paginator from django.db.models import Count, QuerySet from django.shortcuts import render from django.urls import path -from django.utils.translation import ngettext, gettext +from django.utils.translation import gettext, ngettext from huey.contrib.djhuey import HUEY as huey from bookmarks.models import ( @@ -16,10 +17,10 @@ from bookmarks.models import ( Bookmark, BookmarkAsset, BookmarkBundle, - Tag, - UserProfile, - Toast, FeedToken, + Tag, + Toast, + UserProfile, ) from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark @@ -45,12 +46,14 @@ class TaskPaginator(Paginator): # Copied from Huey's SqliteStorage with some modifications to allow pagination def enqueued_items(self, limit, offset): - to_bytes = lambda b: bytes(b) if not isinstance(b, bytes) else b + def to_bytes(b): + return bytes(b) if not isinstance(b, bytes) else b + sql = "select data from task where queue=? order by priority desc, id limit ? offset ?" params = (huey.storage.name, limit, offset) serialized_tasks = [ - to_bytes(i) for i, in huey.storage.sql(sql, params, results=True) + to_bytes(i) for (i,) in huey.storage.sql(sql, params, results=True) ] return [huey.deserialize_task(task) for task in serialized_tasks] @@ -295,7 +298,7 @@ class AdminCustomUser(UserAdmin): def get_inline_instances(self, request, obj=None): if not obj: return list() - return super(AdminCustomUser, self).get_inline_instances(request, obj) + return super().get_inline_instances(request, obj) class AdminToast(admin.ModelAdmin): diff --git a/bookmarks/api/auth.py b/bookmarks/api/auth.py index 7b06ba9..7810acb 100644 --- a/bookmarks/api/auth.py +++ b/bookmarks/api/auth.py @@ -33,6 +33,6 @@ class LinkdingTokenAuthentication(TokenAuthentication): msg = _( "Invalid token header. Token string should not contain invalid characters." ) - raise exceptions.AuthenticationFailed(msg) + raise exceptions.AuthenticationFailed(msg) from None return self.authenticate_credentials(token) diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index ee44f9c..9dbd4f6 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -4,29 +4,29 @@ import os from django.conf import settings from django.http import Http404, StreamingHttpResponse -from rest_framework import viewsets, mixins, status +from rest_framework import mixins, status, viewsets from rest_framework.decorators import action from rest_framework.permissions import AllowAny from rest_framework.response import Response -from rest_framework.routers import SimpleRouter, DefaultRouter +from rest_framework.routers import DefaultRouter, SimpleRouter from bookmarks import queries from bookmarks.api.serializers import ( - BookmarkSerializer, BookmarkAssetSerializer, + BookmarkBundleSerializer, + BookmarkSerializer, TagSerializer, UserProfileSerializer, - BookmarkBundleSerializer, ) from bookmarks.models import ( Bookmark, BookmarkAsset, + BookmarkBundle, BookmarkSearch, Tag, User, - BookmarkBundle, ) -from bookmarks.services import assets, bookmarks, bundles, auto_tagging, website_loader +from bookmarks.services import assets, auto_tagging, bookmarks, bundles, website_loader from bookmarks.type_defs import HttpRequest from bookmarks.views import access @@ -197,7 +197,7 @@ class BookmarkAssetViewSet( file_stream = ( gzip.GzipFile(file_path, mode="rb") if asset.gzip - else open(file_path, "rb") + else open(file_path, "rb") # noqa: SIM115 ) response = StreamingHttpResponse(file_stream, content_type=content_type) response["Content-Disposition"] = ( @@ -205,7 +205,7 @@ class BookmarkAssetViewSet( ) return response except FileNotFoundError: - raise Http404("Asset file does not exist") + raise Http404("Asset file does not exist") from None except Exception as e: logger.error( f"Failed to download asset. bookmark_id={bookmark_id}, asset_id={pk}", diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index ecc9b03..4542abb 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -1,4 +1,4 @@ -from django.db.models import Max, prefetch_related_objects +from django.db.models import prefetch_related_objects from django.templatetags.static import static from rest_framework import serializers from rest_framework.serializers import ListSerializer @@ -6,10 +6,10 @@ from rest_framework.serializers import ListSerializer from bookmarks.models import ( Bookmark, BookmarkAsset, - Tag, - build_tag_string, - UserProfile, BookmarkBundle, + Tag, + UserProfile, + build_tag_string, ) from bookmarks.services import bookmarks, bundles from bookmarks.services.tags import get_or_create_tag @@ -56,7 +56,7 @@ class BookmarkBundleSerializer(serializers.ModelSerializer): def create(self, validated_data): bundle = BookmarkBundle(**validated_data) - bundle.order = validated_data["order"] if "order" in validated_data else None + bundle.order = validated_data.get("order", None) return bundles.create_bundle(bundle, self.context["user"]) diff --git a/bookmarks/apps.py b/bookmarks/apps.py index b58c2d9..46264ca 100644 --- a/bookmarks/apps.py +++ b/bookmarks/apps.py @@ -6,4 +6,5 @@ class BookmarksConfig(AppConfig): def ready(self): # Register signal handlers - import bookmarks.signals + # noinspection PyUnusedImports + import bookmarks.signals # noqa: F401 diff --git a/bookmarks/feeds.py b/bookmarks/feeds.py index 572c617..e7cd1e1 100644 --- a/bookmarks/feeds.py +++ b/bookmarks/feeds.py @@ -50,10 +50,7 @@ class BaseBookmarksFeed(Feed): def items(self, context: FeedContext): limit = context.request.GET.get("limit", 100) - if limit: - data = context.query_set[: int(limit)] - else: - data = list(context.query_set) + data = context.query_set[: int(limit)] if limit else list(context.query_set) prefetch_related_objects(data, "tags") return data diff --git a/bookmarks/forms.py b/bookmarks/forms.py index 3062db0..da58d0a 100644 --- a/bookmarks/forms.py +++ b/bookmarks/forms.py @@ -167,7 +167,9 @@ class TagMergeForm(forms.Form): try: target_tag = Tag.objects.get(name__iexact=target_tag_name, owner=self.user) except Tag.DoesNotExist: - raise forms.ValidationError(f'Tag "{target_tag_name}" does not exist.') + raise forms.ValidationError( + f'Tag "{target_tag_name}" does not exist.' + ) from None return target_tag @@ -184,7 +186,9 @@ class TagMergeForm(forms.Form): tag = Tag.objects.get(name__iexact=tag_name, owner=self.user) merge_tags.append(tag) except Tag.DoesNotExist: - raise forms.ValidationError(f'Tag "{tag_name}" does not exist.') + raise forms.ValidationError( + f'Tag "{tag_name}" does not exist.' + ) from None target_tag = self.cleaned_data.get("target_tag") if target_tag and target_tag in merge_tags: diff --git a/bookmarks/management/commands/backup.py b/bookmarks/management/commands/backup.py index 8ad1c1c..6fb71e0 100644 --- a/bookmarks/management/commands/backup.py +++ b/bookmarks/management/commands/backup.py @@ -1,5 +1,5 @@ -import sqlite3 import os +import sqlite3 from django.core.management.base import BaseCommand @@ -14,7 +14,7 @@ class Command(BaseCommand): destination = options["destination"] def progress(status, remaining, total): - self.stdout.write(f"Copied {total-remaining} of {total} pages...") + self.stdout.write(f"Copied {total - remaining} of {total} pages...") source_db = sqlite3.connect(os.path.join("data", "db.sqlite3")) backup_db = sqlite3.connect(destination) diff --git a/bookmarks/management/commands/create_initial_superuser.py b/bookmarks/management/commands/create_initial_superuser.py index 42b6440..b72c694 100644 --- a/bookmarks/management/commands/create_initial_superuser.py +++ b/bookmarks/management/commands/create_initial_superuser.py @@ -1,8 +1,8 @@ -import os import logging +import os -from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand logger = logging.getLogger(__name__) diff --git a/bookmarks/management/commands/ensure_superuser.py b/bookmarks/management/commands/ensure_superuser.py index 92656f8..63ee604 100644 --- a/bookmarks/management/commands/ensure_superuser.py +++ b/bookmarks/management/commands/ensure_superuser.py @@ -1,5 +1,5 @@ -from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand class Command(BaseCommand): diff --git a/bookmarks/management/commands/full_backup.py b/bookmarks/management/commands/full_backup.py index 642c89e..edad0b8 100644 --- a/bookmarks/management/commands/full_backup.py +++ b/bookmarks/management/commands/full_backup.py @@ -1,5 +1,5 @@ -import sqlite3 import os +import sqlite3 import tempfile import zipfile @@ -65,7 +65,7 @@ class Command(BaseCommand): def backup_database(self, backup_db_file): def progress(status, remaining, total): - self.stdout.write(f"Copied {total-remaining} of {total} pages...") + self.stdout.write(f"Copied {total - remaining} of {total} pages...") source_db = sqlite3.connect(os.path.join("data", "db.sqlite3")) backup_db = sqlite3.connect(backup_db_file) diff --git a/bookmarks/management/commands/generate_secret_key.py b/bookmarks/management/commands/generate_secret_key.py index f62749e..036df1b 100644 --- a/bookmarks/management/commands/generate_secret_key.py +++ b/bookmarks/management/commands/generate_secret_key.py @@ -4,7 +4,6 @@ import os from django.core.management.base import BaseCommand from django.core.management.utils import get_random_secret_key - logger = logging.getLogger(__name__) @@ -15,10 +14,10 @@ class Command(BaseCommand): secret_key_file = os.path.join("data", "secretkey.txt") if os.path.exists(secret_key_file): - logger.info(f"Secret key file already exists") + logger.info("Secret key file already exists") return secret_key = get_random_secret_key() with open(secret_key_file, "w") as f: f.write(secret_key) - logger.info(f"Generated secret key file") + logger.info("Generated secret key file") diff --git a/bookmarks/management/commands/migrate_tasks.py b/bookmarks/management/commands/migrate_tasks.py index 06b0db2..16964e4 100644 --- a/bookmarks/management/commands/migrate_tasks.py +++ b/bookmarks/management/commands/migrate_tasks.py @@ -1,7 +1,7 @@ +import importlib import json import os import sqlite3 -import importlib from django.core.management.base import BaseCommand diff --git a/bookmarks/middlewares.py b/bookmarks/middlewares.py index 7469d2e..9c48d7b 100644 --- a/bookmarks/middlewares.py +++ b/bookmarks/middlewares.py @@ -1,7 +1,7 @@ from django.conf import settings from django.contrib.auth.middleware import RemoteUserMiddleware -from bookmarks.models import UserProfile, GlobalSettings +from bookmarks.models import GlobalSettings, UserProfile class CustomRemoteUserMiddleware(RemoteUserMiddleware): @@ -22,7 +22,7 @@ class LinkdingMiddleware: # add global settings to request try: global_settings = GlobalSettings.get() - except: + except Exception: global_settings = default_global_settings request.global_settings = global_settings diff --git a/bookmarks/migrations/0001_initial.py b/bookmarks/migrations/0001_initial.py index 3c5dc85..8f4e88d 100644 --- a/bookmarks/migrations/0001_initial.py +++ b/bookmarks/migrations/0001_initial.py @@ -1,12 +1,11 @@ # Generated by Django 2.2.2 on 2019-06-28 23:49 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/bookmarks/migrations/0002_auto_20190629_2303.py b/bookmarks/migrations/0002_auto_20190629_2303.py index c63a2f3..3c76a9d 100644 --- a/bookmarks/migrations/0002_auto_20190629_2303.py +++ b/bookmarks/migrations/0002_auto_20190629_2303.py @@ -1,12 +1,11 @@ # Generated by Django 2.2.2 on 2019-06-29 23:03 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("bookmarks", "0001_initial"), diff --git a/bookmarks/migrations/0003_auto_20200913_0656.py b/bookmarks/migrations/0003_auto_20200913_0656.py index 78ae8c3..c4dd66a 100644 --- a/bookmarks/migrations/0003_auto_20200913_0656.py +++ b/bookmarks/migrations/0003_auto_20200913_0656.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0002_auto_20190629_2303"), ] diff --git a/bookmarks/migrations/0004_auto_20200926_1028.py b/bookmarks/migrations/0004_auto_20200926_1028.py index 9b2f196..6e95334 100644 --- a/bookmarks/migrations/0004_auto_20200926_1028.py +++ b/bookmarks/migrations/0004_auto_20200926_1028.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0003_auto_20200913_0656"), ] diff --git a/bookmarks/migrations/0005_auto_20210103_1212.py b/bookmarks/migrations/0005_auto_20210103_1212.py index d0d314a..79fee4d 100644 --- a/bookmarks/migrations/0005_auto_20210103_1212.py +++ b/bookmarks/migrations/0005_auto_20210103_1212.py @@ -1,11 +1,11 @@ # Generated by Django 2.2.13 on 2021-01-03 12:12 -import bookmarks.validators from django.db import migrations, models +import bookmarks.validators + class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0004_auto_20200926_1028"), ] diff --git a/bookmarks/migrations/0006_bookmark_is_archived.py b/bookmarks/migrations/0006_bookmark_is_archived.py index ab713f7..dff42a3 100644 --- a/bookmarks/migrations/0006_bookmark_is_archived.py +++ b/bookmarks/migrations/0006_bookmark_is_archived.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0005_auto_20210103_1212"), ] diff --git a/bookmarks/migrations/0007_userprofile.py b/bookmarks/migrations/0007_userprofile.py index c9b229c..82c5486 100644 --- a/bookmarks/migrations/0007_userprofile.py +++ b/bookmarks/migrations/0007_userprofile.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.18 on 2021-03-26 22:39 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion def forwards(apps, schema_editor): diff --git a/bookmarks/migrations/0008_userprofile_bookmark_date_display.py b/bookmarks/migrations/0008_userprofile_bookmark_date_display.py index 41dce4f..e03f078 100644 --- a/bookmarks/migrations/0008_userprofile_bookmark_date_display.py +++ b/bookmarks/migrations/0008_userprofile_bookmark_date_display.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0007_userprofile"), ] diff --git a/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py b/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py index 0f35c64..4c1708e 100644 --- a/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py +++ b/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0008_userprofile_bookmark_date_display"), ] diff --git a/bookmarks/migrations/0010_userprofile_bookmark_link_target.py b/bookmarks/migrations/0010_userprofile_bookmark_link_target.py index 3c4e2e7..88f3c61 100644 --- a/bookmarks/migrations/0010_userprofile_bookmark_link_target.py +++ b/bookmarks/migrations/0010_userprofile_bookmark_link_target.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0009_bookmark_web_archive_snapshot_url"), ] diff --git a/bookmarks/migrations/0011_userprofile_web_archive_integration.py b/bookmarks/migrations/0011_userprofile_web_archive_integration.py index dbc6c58..048c187 100644 --- a/bookmarks/migrations/0011_userprofile_web_archive_integration.py +++ b/bookmarks/migrations/0011_userprofile_web_archive_integration.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0010_userprofile_bookmark_link_target"), ] diff --git a/bookmarks/migrations/0012_toast.py b/bookmarks/migrations/0012_toast.py index 0fb0569..9ad206e 100644 --- a/bookmarks/migrations/0012_toast.py +++ b/bookmarks/migrations/0012_toast.py @@ -1,12 +1,11 @@ # Generated by Django 3.2.6 on 2022-01-08 19:24 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("bookmarks", "0011_userprofile_web_archive_integration"), diff --git a/bookmarks/migrations/0013_web_archive_optin_toast.py b/bookmarks/migrations/0013_web_archive_optin_toast.py index 93627b9..0eae53c 100644 --- a/bookmarks/migrations/0013_web_archive_optin_toast.py +++ b/bookmarks/migrations/0013_web_archive_optin_toast.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.6 on 2022-01-08 19:27 -from django.db import migrations from django.contrib.auth import get_user_model +from django.db import migrations from bookmarks.models import Toast diff --git a/bookmarks/migrations/0015_feedtoken.py b/bookmarks/migrations/0015_feedtoken.py index 00d155d..e58fb6d 100644 --- a/bookmarks/migrations/0015_feedtoken.py +++ b/bookmarks/migrations/0015_feedtoken.py @@ -1,12 +1,11 @@ # Generated by Django 3.2.13 on 2022-07-23 20:35 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("bookmarks", "0014_alter_bookmark_unread"), diff --git a/bookmarks/migrations/0016_bookmark_shared.py b/bookmarks/migrations/0016_bookmark_shared.py index c05ee32..b446cf7 100644 --- a/bookmarks/migrations/0016_bookmark_shared.py +++ b/bookmarks/migrations/0016_bookmark_shared.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0015_feedtoken"), ] diff --git a/bookmarks/migrations/0017_userprofile_enable_sharing.py b/bookmarks/migrations/0017_userprofile_enable_sharing.py index 7a094eb..36fa119 100644 --- a/bookmarks/migrations/0017_userprofile_enable_sharing.py +++ b/bookmarks/migrations/0017_userprofile_enable_sharing.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0016_bookmark_shared"), ] diff --git a/bookmarks/migrations/0018_bookmark_favicon_file.py b/bookmarks/migrations/0018_bookmark_favicon_file.py index ce99fcb..0a31ae1 100644 --- a/bookmarks/migrations/0018_bookmark_favicon_file.py +++ b/bookmarks/migrations/0018_bookmark_favicon_file.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0017_userprofile_enable_sharing"), ] diff --git a/bookmarks/migrations/0019_userprofile_enable_favicons.py b/bookmarks/migrations/0019_userprofile_enable_favicons.py index b0fc382..cc082e3 100644 --- a/bookmarks/migrations/0019_userprofile_enable_favicons.py +++ b/bookmarks/migrations/0019_userprofile_enable_favicons.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0018_bookmark_favicon_file"), ] diff --git a/bookmarks/migrations/0020_userprofile_tag_search.py b/bookmarks/migrations/0020_userprofile_tag_search.py index 13d9adf..9cd9c88 100644 --- a/bookmarks/migrations/0020_userprofile_tag_search.py +++ b/bookmarks/migrations/0020_userprofile_tag_search.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0019_userprofile_enable_favicons"), ] diff --git a/bookmarks/migrations/0021_userprofile_display_url.py b/bookmarks/migrations/0021_userprofile_display_url.py index a4bcf7c..05be048 100644 --- a/bookmarks/migrations/0021_userprofile_display_url.py +++ b/bookmarks/migrations/0021_userprofile_display_url.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0020_userprofile_tag_search"), ] diff --git a/bookmarks/migrations/0022_bookmark_notes.py b/bookmarks/migrations/0022_bookmark_notes.py index b98df83..c378503 100644 --- a/bookmarks/migrations/0022_bookmark_notes.py +++ b/bookmarks/migrations/0022_bookmark_notes.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0021_userprofile_display_url"), ] diff --git a/bookmarks/migrations/0023_userprofile_permanent_notes.py b/bookmarks/migrations/0023_userprofile_permanent_notes.py index 6b7a1d5..0115e3d 100644 --- a/bookmarks/migrations/0023_userprofile_permanent_notes.py +++ b/bookmarks/migrations/0023_userprofile_permanent_notes.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0022_bookmark_notes"), ] diff --git a/bookmarks/migrations/0024_userprofile_enable_public_sharing.py b/bookmarks/migrations/0024_userprofile_enable_public_sharing.py index 0a964d7..3611b17 100644 --- a/bookmarks/migrations/0024_userprofile_enable_public_sharing.py +++ b/bookmarks/migrations/0024_userprofile_enable_public_sharing.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0023_userprofile_permanent_notes"), ] diff --git a/bookmarks/migrations/0025_userprofile_search_preferences.py b/bookmarks/migrations/0025_userprofile_search_preferences.py index 4cf1223..a3521bd 100644 --- a/bookmarks/migrations/0025_userprofile_search_preferences.py +++ b/bookmarks/migrations/0025_userprofile_search_preferences.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0024_userprofile_enable_public_sharing"), ] diff --git a/bookmarks/migrations/0026_userprofile_custom_css.py b/bookmarks/migrations/0026_userprofile_custom_css.py index f123246..9f0fdf0 100644 --- a/bookmarks/migrations/0026_userprofile_custom_css.py +++ b/bookmarks/migrations/0026_userprofile_custom_css.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0025_userprofile_search_preferences"), ] diff --git a/bookmarks/migrations/0027_userprofile_bookmark_description_display_and_more.py b/bookmarks/migrations/0027_userprofile_bookmark_description_display_and_more.py index 917fbba..fed2da8 100644 --- a/bookmarks/migrations/0027_userprofile_bookmark_description_display_and_more.py +++ b/bookmarks/migrations/0027_userprofile_bookmark_description_display_and_more.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0026_userprofile_custom_css"), ] diff --git a/bookmarks/migrations/0028_userprofile_display_archive_bookmark_action_and_more.py b/bookmarks/migrations/0028_userprofile_display_archive_bookmark_action_and_more.py index 6af3d30..8e5ec9a 100644 --- a/bookmarks/migrations/0028_userprofile_display_archive_bookmark_action_and_more.py +++ b/bookmarks/migrations/0028_userprofile_display_archive_bookmark_action_and_more.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0027_userprofile_bookmark_description_display_and_more"), ] diff --git a/bookmarks/migrations/0029_bookmark_list_actions_toast.py b/bookmarks/migrations/0029_bookmark_list_actions_toast.py index 05891b4..7d0a11c 100644 --- a/bookmarks/migrations/0029_bookmark_list_actions_toast.py +++ b/bookmarks/migrations/0029_bookmark_list_actions_toast.py @@ -1,7 +1,7 @@ # Generated by Django 5.0.2 on 2024-03-29 21:25 -from django.db import migrations from django.contrib.auth import get_user_model +from django.db import migrations from bookmarks.models import Toast @@ -9,7 +9,6 @@ User = get_user_model() def forwards(apps, schema_editor): - for user in User.objects.all(): toast = Toast( key="bookmark_list_actions_hint", @@ -24,7 +23,6 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0028_userprofile_display_archive_bookmark_action_and_more"), ] diff --git a/bookmarks/migrations/0030_bookmarkasset.py b/bookmarks/migrations/0030_bookmarkasset.py index 6cd8e15..6982ee3 100644 --- a/bookmarks/migrations/0030_bookmarkasset.py +++ b/bookmarks/migrations/0030_bookmarkasset.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0029_bookmark_list_actions_toast"), ] diff --git a/bookmarks/migrations/0031_userprofile_enable_automatic_html_snapshots.py b/bookmarks/migrations/0031_userprofile_enable_automatic_html_snapshots.py index 601a545..46afdd9 100644 --- a/bookmarks/migrations/0031_userprofile_enable_automatic_html_snapshots.py +++ b/bookmarks/migrations/0031_userprofile_enable_automatic_html_snapshots.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0030_bookmarkasset"), ] diff --git a/bookmarks/migrations/0032_html_snapshots_hint_toast.py b/bookmarks/migrations/0032_html_snapshots_hint_toast.py index 546ac51..372ecca 100644 --- a/bookmarks/migrations/0032_html_snapshots_hint_toast.py +++ b/bookmarks/migrations/0032_html_snapshots_hint_toast.py @@ -1,7 +1,7 @@ # Generated by Django 5.0.2 on 2024-04-01 12:17 -from django.db import migrations from django.contrib.auth import get_user_model +from django.db import migrations from bookmarks.models import Toast @@ -9,7 +9,6 @@ User = get_user_model() def forwards(apps, schema_editor): - for user in User.objects.all(): toast = Toast( key="html_snapshots_hint", @@ -24,7 +23,6 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0031_userprofile_enable_automatic_html_snapshots"), ] diff --git a/bookmarks/migrations/0033_userprofile_default_mark_unread.py b/bookmarks/migrations/0033_userprofile_default_mark_unread.py index e5bcfe5..4bbc03b 100644 --- a/bookmarks/migrations/0033_userprofile_default_mark_unread.py +++ b/bookmarks/migrations/0033_userprofile_default_mark_unread.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0032_html_snapshots_hint_toast"), ] diff --git a/bookmarks/migrations/0034_bookmark_preview_image_file_and_more.py b/bookmarks/migrations/0034_bookmark_preview_image_file_and_more.py index 454d0d1..b8686d2 100644 --- a/bookmarks/migrations/0034_bookmark_preview_image_file_and_more.py +++ b/bookmarks/migrations/0034_bookmark_preview_image_file_and_more.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0033_userprofile_default_mark_unread"), ] diff --git a/bookmarks/migrations/0035_userprofile_tag_grouping.py b/bookmarks/migrations/0035_userprofile_tag_grouping.py index dda5bc3..556eca2 100644 --- a/bookmarks/migrations/0035_userprofile_tag_grouping.py +++ b/bookmarks/migrations/0035_userprofile_tag_grouping.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0034_bookmark_preview_image_file_and_more"), ] diff --git a/bookmarks/migrations/0036_userprofile_auto_tagging_rules.py b/bookmarks/migrations/0036_userprofile_auto_tagging_rules.py index 4454739..91553cd 100644 --- a/bookmarks/migrations/0036_userprofile_auto_tagging_rules.py +++ b/bookmarks/migrations/0036_userprofile_auto_tagging_rules.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0035_userprofile_tag_grouping"), ] diff --git a/bookmarks/migrations/0037_globalsettings.py b/bookmarks/migrations/0037_globalsettings.py index 48aa8e5..1725774 100644 --- a/bookmarks/migrations/0037_globalsettings.py +++ b/bookmarks/migrations/0037_globalsettings.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0036_userprofile_auto_tagging_rules"), ] diff --git a/bookmarks/migrations/0038_globalsettings_guest_profile_user.py b/bookmarks/migrations/0038_globalsettings_guest_profile_user.py index 6950e9c..ece1604 100644 --- a/bookmarks/migrations/0038_globalsettings_guest_profile_user.py +++ b/bookmarks/migrations/0038_globalsettings_guest_profile_user.py @@ -6,7 +6,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0037_globalsettings"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), diff --git a/bookmarks/migrations/0039_globalsettings_enable_link_prefetch.py b/bookmarks/migrations/0039_globalsettings_enable_link_prefetch.py index b18ec95..cc472fb 100644 --- a/bookmarks/migrations/0039_globalsettings_enable_link_prefetch.py +++ b/bookmarks/migrations/0039_globalsettings_enable_link_prefetch.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0038_globalsettings_guest_profile_user"), ] diff --git a/bookmarks/migrations/0040_userprofile_items_per_page_and_more.py b/bookmarks/migrations/0040_userprofile_items_per_page_and_more.py index 0b11247..9a3bc46 100644 --- a/bookmarks/migrations/0040_userprofile_items_per_page_and_more.py +++ b/bookmarks/migrations/0040_userprofile_items_per_page_and_more.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0039_globalsettings_enable_link_prefetch"), ] diff --git a/bookmarks/migrations/0041_merge_metadata.py b/bookmarks/migrations/0041_merge_metadata.py index 81e3ab9..92bdcb8 100644 --- a/bookmarks/migrations/0041_merge_metadata.py +++ b/bookmarks/migrations/0041_merge_metadata.py @@ -10,9 +10,9 @@ from bookmarks.models import Bookmark def forwards(apps, schema_editor): Bookmark.objects.filter( Q(title__isnull=True) | Q(title__exact=""), - ).extra( - where=["website_title IS NOT NULL"] - ).update(title=RawSQL("website_title", ())) + ).extra(where=["website_title IS NOT NULL"]).update( + title=RawSQL("website_title", ()) + ) Bookmark.objects.filter( Q(description__isnull=True) | Q(description__exact=""), @@ -26,7 +26,6 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0040_userprofile_items_per_page_and_more"), ] diff --git a/bookmarks/migrations/0042_userprofile_custom_css_hash.py b/bookmarks/migrations/0042_userprofile_custom_css_hash.py index bc027ac..528c9b3 100644 --- a/bookmarks/migrations/0042_userprofile_custom_css_hash.py +++ b/bookmarks/migrations/0042_userprofile_custom_css_hash.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0041_merge_metadata"), ] diff --git a/bookmarks/migrations/0043_userprofile_collapse_side_panel.py b/bookmarks/migrations/0043_userprofile_collapse_side_panel.py index 4a0405f..3ce6fb8 100644 --- a/bookmarks/migrations/0043_userprofile_collapse_side_panel.py +++ b/bookmarks/migrations/0043_userprofile_collapse_side_panel.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0042_userprofile_custom_css_hash"), ] diff --git a/bookmarks/migrations/0044_bookmark_latest_snapshot.py b/bookmarks/migrations/0044_bookmark_latest_snapshot.py index 06f4b86..e9c4325 100644 --- a/bookmarks/migrations/0044_bookmark_latest_snapshot.py +++ b/bookmarks/migrations/0044_bookmark_latest_snapshot.py @@ -25,7 +25,6 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0043_userprofile_collapse_side_panel"), ] diff --git a/bookmarks/migrations/0045_userprofile_hide_bundles_bookmarkbundle.py b/bookmarks/migrations/0045_userprofile_hide_bundles_bookmarkbundle.py index 7be682f..c884258 100644 --- a/bookmarks/migrations/0045_userprofile_hide_bundles_bookmarkbundle.py +++ b/bookmarks/migrations/0045_userprofile_hide_bundles_bookmarkbundle.py @@ -6,7 +6,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0044_bookmark_latest_snapshot"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), diff --git a/bookmarks/migrations/0046_add_url_normalized_field.py b/bookmarks/migrations/0046_add_url_normalized_field.py index 2b689ad..6b849f0 100644 --- a/bookmarks/migrations/0046_add_url_normalized_field.py +++ b/bookmarks/migrations/0046_add_url_normalized_field.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0045_userprofile_hide_bundles_bookmarkbundle"), ] diff --git a/bookmarks/migrations/0047_populate_url_normalized_field.py b/bookmarks/migrations/0047_populate_url_normalized_field.py index bb4fae2..f2a1ac9 100644 --- a/bookmarks/migrations/0047_populate_url_normalized_field.py +++ b/bookmarks/migrations/0047_populate_url_normalized_field.py @@ -1,6 +1,7 @@ # Generated by Django 5.2.3 on 2025-08-22 08:28 from django.db import migrations, transaction + from bookmarks.utils import normalize_url @@ -25,7 +26,6 @@ def reverse_populate_url_normalized(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0046_add_url_normalized_field"), ] diff --git a/bookmarks/migrations/0048_userprofile_default_mark_shared.py b/bookmarks/migrations/0048_userprofile_default_mark_shared.py index 0b149a0..bf1f0ad 100644 --- a/bookmarks/migrations/0048_userprofile_default_mark_shared.py +++ b/bookmarks/migrations/0048_userprofile_default_mark_shared.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0047_populate_url_normalized_field"), ] diff --git a/bookmarks/migrations/0049_userprofile_legacy_search.py b/bookmarks/migrations/0049_userprofile_legacy_search.py index 84b0bb1..1136d3d 100644 --- a/bookmarks/migrations/0049_userprofile_legacy_search.py +++ b/bookmarks/migrations/0049_userprofile_legacy_search.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0048_userprofile_default_mark_shared"), ] diff --git a/bookmarks/migrations/0050_new_search_toast.py b/bookmarks/migrations/0050_new_search_toast.py index 623e6c5..2b6decd 100644 --- a/bookmarks/migrations/0050_new_search_toast.py +++ b/bookmarks/migrations/0050_new_search_toast.py @@ -9,7 +9,6 @@ User = get_user_model() def forwards(apps, schema_editor): - for user in User.objects.all(): toast = Toast( key="new_search_toast", @@ -24,7 +23,6 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0049_userprofile_legacy_search"), ] diff --git a/bookmarks/migrations/0051_fix_normalized_url.py b/bookmarks/migrations/0051_fix_normalized_url.py index adc6e9d..15db065 100644 --- a/bookmarks/migrations/0051_fix_normalized_url.py +++ b/bookmarks/migrations/0051_fix_normalized_url.py @@ -1,6 +1,7 @@ # Generated by Django 5.2.5 on 2025-10-11 08:46 from django.db import migrations + from bookmarks.utils import normalize_url @@ -21,7 +22,6 @@ def reverse_fix_url_normalized(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0050_new_search_toast"), ] diff --git a/bookmarks/migrations/0052_apitoken.py b/bookmarks/migrations/0052_apitoken.py index b3fbc69..b2da27e 100644 --- a/bookmarks/migrations/0052_apitoken.py +++ b/bookmarks/migrations/0052_apitoken.py @@ -6,7 +6,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0051_fix_normalized_url"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), diff --git a/bookmarks/migrations/0053_migrate_api_tokens.py b/bookmarks/migrations/0053_migrate_api_tokens.py index 36a983b..f8d0997 100644 --- a/bookmarks/migrations/0053_migrate_api_tokens.py +++ b/bookmarks/migrations/0053_migrate_api_tokens.py @@ -21,7 +21,6 @@ def migrate_tokens_reverse(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("bookmarks", "0052_apitoken"), ("authtoken", "0004_alter_tokenproxy_options"), diff --git a/bookmarks/models.py b/bookmarks/models.py index e0e8ff2..9dd93de 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -1,20 +1,19 @@ +import binascii import hashlib import logging import os -from typing import List -import binascii from django import forms from django.conf import settings from django.contrib.auth.models import User from django.core.validators import MinValueValidator from django.db import models from django.db.models import Q -from django.db.models.signals import post_save, post_delete +from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.http import QueryDict -from bookmarks.utils import unique, normalize_url +from bookmarks.utils import normalize_url, unique from bookmarks.validators import BookmarkURLValidator logger = logging.getLogger(__name__) @@ -48,7 +47,7 @@ def parse_tag_string(tag_string: str, delimiter: str = ","): return names -def build_tag_string(tag_names: List[str], delimiter: str = ","): +def build_tag_string(tag_names: list[str], delimiter: str = ","): return delimiter.join(tag_names) @@ -354,8 +353,8 @@ class BookmarkSearchForm(forms.Form): def __init__( self, search: BookmarkSearch, - editable_fields: List[str] = None, - users: List[User] = None, + editable_fields: list[str] = None, + users: list[User] = None, ): super().__init__() editable_fields = editable_fields or [] @@ -640,7 +639,7 @@ class GlobalSettings(models.Model): def save(self, *args, **kwargs): if not self.pk and GlobalSettings.objects.exists(): raise Exception("There is already one instance of GlobalSettings") - return super(GlobalSettings, self).save(*args, **kwargs) + return super().save(*args, **kwargs) class GlobalSettingsForm(forms.ModelForm): @@ -649,5 +648,5 @@ class GlobalSettingsForm(forms.ModelForm): fields = ["landing_page", "guest_profile_user", "enable_link_prefetch"] def __init__(self, *args, **kwargs): - super(GlobalSettingsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["guest_profile_user"].empty_label = "Standard profile" diff --git a/bookmarks/queries.py b/bookmarks/queries.py index e99c294..5780f30 100644 --- a/bookmarks/queries.py +++ b/bookmarks/queries.py @@ -1,9 +1,9 @@ -from typing import Optional +import contextlib from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from django.db.models import Q, QuerySet, Exists, OuterRef, Case, When, CharField +from django.db.models import Case, CharField, Exists, OuterRef, Q, QuerySet, When from django.db.models.expressions import RawSQL from django.db.models.functions import Lower @@ -16,16 +16,16 @@ from bookmarks.models import ( parse_tag_string, ) from bookmarks.services.search_query_parser import ( - parse_search_query, - SearchExpression, - TermExpression, - TagExpression, - SpecialKeywordExpression, AndExpression, - OrExpression, NotExpression, + OrExpression, + SearchExpression, SearchQueryParseError, + SpecialKeywordExpression, + TagExpression, + TermExpression, extract_tag_names_from_query, + parse_search_query, ) from bookmarks.utils import unique @@ -45,7 +45,7 @@ def query_archived_bookmarks( def query_shared_bookmarks( - user: Optional[User], + user: User | None, profile: UserProfile, search: BookmarkSearch, public_only: bool, @@ -215,7 +215,7 @@ def _filter_bundle(query_set: QuerySet, bundle: BookmarkBundle) -> QuerySet: def _base_bookmarks_query( - user: Optional[User], + user: User | None, profile: UserProfile, search: BookmarkSearch, ) -> QuerySet: @@ -227,19 +227,15 @@ def _base_bookmarks_query( # Filter by modified_since if provided if search.modified_since: - try: + # If the date format is invalid, ignore the filter + with contextlib.suppress(ValidationError): query_set = query_set.filter(date_modified__gt=search.modified_since) - except ValidationError: - # If the date format is invalid, ignore the filter - pass # Filter by added_since if provided if search.added_since: - try: + # If the date format is invalid, ignore the filter + with contextlib.suppress(ValidationError): query_set = query_set.filter(date_added__gt=search.added_since) - except ValidationError: - # If the date format is invalid, ignore the filter - pass # Filter by search query if profile.legacy_search: @@ -320,7 +316,7 @@ def query_archived_bookmark_tags( def query_shared_bookmark_tags( - user: Optional[User], + user: User | None, profile: UserProfile, search: BookmarkSearch, public_only: bool, @@ -360,7 +356,7 @@ def get_tags_for_query(user: User, profile: UserProfile, query: str) -> QuerySet def get_shared_tags_for_query( - user: Optional[User], profile: UserProfile, query: str, public_only: bool + user: User | None, profile: UserProfile, query: str, public_only: bool ) -> QuerySet: tag_names = extract_tag_names_from_query(query, profile) diff --git a/bookmarks/services/assets.py b/bookmarks/services/assets.py index ff4a35a..7c0887d 100644 --- a/bookmarks/services/assets.py +++ b/bookmarks/services/assets.py @@ -5,7 +5,7 @@ import shutil from django.conf import settings from django.core.files.uploadedfile import UploadedFile -from django.utils import timezone, formats +from django.utils import formats, timezone from bookmarks.models import Bookmark, BookmarkAsset from bookmarks.services import singlefile diff --git a/bookmarks/services/auto_tagging.py b/bookmarks/services/auto_tagging.py index cdabab6..cd52bcb 100644 --- a/bookmarks/services/auto_tagging.py +++ b/bookmarks/services/auto_tagging.py @@ -1,5 +1,6 @@ -from urllib.parse import urlparse, parse_qs import re +from urllib.parse import parse_qs, urlparse + import idna diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py index 180c8ff..c01b99a 100644 --- a/bookmarks/services/bookmarks.py +++ b/bookmarks/services/bookmarks.py @@ -1,12 +1,9 @@ import logging -from typing import Union from django.utils import timezone from bookmarks.models import Bookmark, User, parse_tag_string -from bookmarks.services import auto_tagging -from bookmarks.services import tasks -from bookmarks.services import website_loader +from bookmarks.services import auto_tagging, tasks, website_loader from bookmarks.services.tags import get_or_create_tags logger = logging.getLogger(__name__) @@ -91,7 +88,7 @@ def archive_bookmark(bookmark: Bookmark): return bookmark -def archive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): +def archive_bookmarks(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -106,7 +103,7 @@ def unarchive_bookmark(bookmark: Bookmark): return bookmark -def unarchive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): +def unarchive_bookmarks(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -114,13 +111,13 @@ def unarchive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): ) -def delete_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): +def delete_bookmarks(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).delete() -def tag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user: User): +def tag_bookmarks(bookmark_ids: [int | str], tag_string: str, current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) owned_bookmark_ids = Bookmark.objects.filter( owner=current_user, id__in=sanitized_bookmark_ids @@ -143,9 +140,7 @@ def tag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user ) -def untag_bookmarks( - bookmark_ids: [Union[int, str]], tag_string: str, current_user: User -): +def untag_bookmarks(bookmark_ids: [int | str], tag_string: str, current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) owned_bookmark_ids = Bookmark.objects.filter( owner=current_user, id__in=sanitized_bookmark_ids @@ -165,7 +160,7 @@ def untag_bookmarks( ) -def mark_bookmarks_as_read(bookmark_ids: [Union[int, str]], current_user: User): +def mark_bookmarks_as_read(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -173,7 +168,7 @@ def mark_bookmarks_as_read(bookmark_ids: [Union[int, str]], current_user: User): ) -def mark_bookmarks_as_unread(bookmark_ids: [Union[int, str]], current_user: User): +def mark_bookmarks_as_unread(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -181,7 +176,7 @@ def mark_bookmarks_as_unread(bookmark_ids: [Union[int, str]], current_user: User ) -def share_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): +def share_bookmarks(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -189,7 +184,7 @@ def share_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): ) -def unshare_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): +def unshare_bookmarks(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( @@ -197,7 +192,7 @@ def unshare_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): ) -def refresh_bookmarks_metadata(bookmark_ids: [Union[int, str]], current_user: User): +def refresh_bookmarks_metadata(bookmark_ids: [int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) owned_bookmarks = Bookmark.objects.filter( owner=current_user, id__in=sanitized_bookmark_ids @@ -208,7 +203,7 @@ def refresh_bookmarks_metadata(bookmark_ids: [Union[int, str]], current_user: Us tasks.load_preview_image(current_user, bookmark) -def create_html_snapshots(bookmark_ids: list[Union[int, str]], current_user: User): +def create_html_snapshots(bookmark_ids: list[int | str], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) owned_bookmarks = Bookmark.objects.filter( owner=current_user, id__in=sanitized_bookmark_ids @@ -246,6 +241,6 @@ def _update_bookmark_tags(bookmark: Bookmark, tag_string: str, user: User): bookmark.tags.set(tags) -def _sanitize_id_list(bookmark_ids: [Union[int, str]]) -> [int]: +def _sanitize_id_list(bookmark_ids: [int | str]) -> [int]: # Convert string ids to int if necessary return [int(bm_id) if isinstance(bm_id, str) else bm_id for bm_id in bookmark_ids] diff --git a/bookmarks/services/exporter.py b/bookmarks/services/exporter.py index bf7cd53..d8c72bf 100644 --- a/bookmarks/services/exporter.py +++ b/bookmarks/services/exporter.py @@ -1,12 +1,11 @@ import html -from typing import List from bookmarks.models import Bookmark -BookmarkDocument = List[str] +BookmarkDocument = list[str] -def export_netscape_html(bookmarks: List[Bookmark]): +def export_netscape_html(bookmarks: list[Bookmark]): doc = [] append_header(doc) append_list_start(doc) diff --git a/bookmarks/services/importer.py b/bookmarks/services/importer.py index 05460f3..9d2b493 100644 --- a/bookmarks/services/importer.py +++ b/bookmarks/services/importer.py @@ -1,13 +1,12 @@ import logging from dataclasses import dataclass -from typing import List from django.contrib.auth.models import User from django.utils import timezone from bookmarks.models import Bookmark, Tag from bookmarks.services import tasks -from bookmarks.services.parser import parse, NetscapeBookmark +from bookmarks.services.parser import NetscapeBookmark, parse from bookmarks.utils import normalize_url, parse_timestamp logger = logging.getLogger(__name__) @@ -41,13 +40,13 @@ class TagCache: else: return None - def get_all(self, tag_names: List[str]): + def get_all(self, tag_names: list[str]): result = [] for tag_name in tag_names: tag = self.get(tag_name) # Tag may not have been created if tag name exceeded maximum length # Prevent returning duplicates - if tag and not (tag in result): + if tag and tag not in result: result.append(tag) return result @@ -57,14 +56,16 @@ class TagCache: def import_netscape_html( - html: str, user: User, options: ImportOptions = ImportOptions() + html: str, user: User, options: ImportOptions | None = None ) -> ImportResult: + if options is None: + options = ImportOptions() result = ImportResult() import_start = timezone.now() try: netscape_bookmarks = parse(html) - except: + except Exception: logging.exception("Could not read bookmarks file.") raise @@ -91,7 +92,7 @@ def import_netscape_html( return result -def _create_missing_tags(netscape_bookmarks: List[NetscapeBookmark], user: User): +def _create_missing_tags(netscape_bookmarks: list[NetscapeBookmark], user: User): tag_cache = TagCache(user) tags_to_create = [] @@ -114,7 +115,7 @@ def _create_missing_tags(netscape_bookmarks: List[NetscapeBookmark], user: User) Tag.objects.bulk_create(tags_to_create) -def _get_batches(items: List, batch_size: int): +def _get_batches(items: list, batch_size: int): batches = [] offset = 0 num_items = len(items) @@ -129,7 +130,7 @@ def _get_batches(items: List, batch_size: int): def _import_batch( - netscape_bookmarks: List[NetscapeBookmark], + netscape_bookmarks: list[NetscapeBookmark], user: User, options: ImportOptions, tag_cache: TagCache, @@ -172,7 +173,7 @@ def _import_batch( bookmarks_to_create.append(bookmark) result.success = result.success + 1 - except: + except Exception: shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + "..." logging.exception("Error importing bookmark: " + shortened_bookmark_tag_str) result.failed = result.failed + 1 diff --git a/bookmarks/services/monolith.py b/bookmarks/services/monolith.py index 9c54cbc..1721277 100644 --- a/bookmarks/services/monolith.py +++ b/bookmarks/services/monolith.py @@ -1,7 +1,7 @@ import gzip +import os import shutil import subprocess -import os from django.conf import settings @@ -30,4 +30,4 @@ def create_snapshot(url: str, filepath: str): os.remove(temp_filepath) except subprocess.CalledProcessError as error: - raise MonolithError(f"Failed to create snapshot: {error.stderr}") + raise MonolithError(f"Failed to create snapshot: {error.stderr}") from error diff --git a/bookmarks/services/parser.py b/bookmarks/services/parser.py index 62d1559..7175b3b 100644 --- a/bookmarks/services/parser.py +++ b/bookmarks/services/parser.py @@ -1,6 +1,6 @@ +import contextlib from dataclasses import dataclass from html.parser import HTMLParser -from typing import Dict, List from bookmarks.models import parse_tag_string @@ -13,7 +13,7 @@ class NetscapeBookmark: notes: str date_added: str date_modified: str - tag_names: List[str] + tag_names: list[str] to_read: bool private: bool archived: bool @@ -56,17 +56,15 @@ class BookmarkParser(HTMLParser): def handle_end_dl(self): self.add_bookmark() - def handle_start_dt(self, attrs: Dict[str, str]): + def handle_start_dt(self, attrs: dict[str, str]): self.add_bookmark() - def handle_start_a(self, attrs: Dict[str, str]): + def handle_start_a(self, attrs: dict[str, str]): vars(self).update(attrs) tag_names = parse_tag_string(self.tags) archived = "linkding:bookmarks.archived" in self.tags - try: + with contextlib.suppress(ValueError): tag_names.remove("linkding:bookmarks.archived") - except ValueError: - pass self.bookmark = NetscapeBookmark( href=self.href, @@ -109,7 +107,7 @@ class BookmarkParser(HTMLParser): self.private = "" -def parse(html: str) -> List[NetscapeBookmark]: +def parse(html: str) -> list[NetscapeBookmark]: parser = BookmarkParser() parser.feed(html) return parser.bookmarks diff --git a/bookmarks/services/preview_image_loader.py b/bookmarks/services/preview_image_loader.py index 895f6ba..797cfa3 100644 --- a/bookmarks/services/preview_image_loader.py +++ b/bookmarks/services/preview_image_loader.py @@ -1,11 +1,12 @@ +import hashlib import logging import mimetypes import os.path -import hashlib from pathlib import Path import requests from django.conf import settings + from bookmarks.services import website_loader logger = logging.getLogger(__name__) diff --git a/bookmarks/services/search_query_parser.py b/bookmarks/services/search_query_parser.py index 25415f4..53c9bb2 100644 --- a/bookmarks/services/search_query_parser.py +++ b/bookmarks/services/search_query_parser.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from enum import Enum -from typing import List, Optional from bookmarks.models import UserProfile @@ -124,7 +123,7 @@ class SearchQueryTokenizer: return keyword - def tokenize(self) -> List[Token]: + def tokenize(self) -> list[Token]: """Convert the query string into a list of tokens.""" tokens = [] @@ -221,7 +220,7 @@ class SearchQueryParseError(Exception): class SearchQueryParser: - def __init__(self, tokens: List[Token]): + def __init__(self, tokens: list[Token]): self.tokens = tokens self.position = 0 self.current_token = tokens[0] if tokens else Token(TokenType.EOF, "", 0) @@ -244,7 +243,7 @@ class SearchQueryParser: self.current_token.position, ) - def parse(self) -> Optional[SearchExpression]: + def parse(self) -> SearchExpression | None: """Parse the tokens into an AST.""" if not self.tokens or ( len(self.tokens) == 1 and self.tokens[0].type == TokenType.EOF @@ -283,7 +282,6 @@ class SearchQueryParser: TokenType.LPAREN, TokenType.NOT, ]: - if self.current_token.type == TokenType.AND: self.advance() # consume explicit AND # else: implicit AND (don't advance token) @@ -328,7 +326,7 @@ class SearchQueryParser: ) -def parse_search_query(query: str) -> Optional[SearchExpression]: +def parse_search_query(query: str) -> SearchExpression | None: if not query or not query.strip(): return None @@ -342,9 +340,9 @@ def _needs_parentheses(expr: SearchExpression, parent_type: type) -> bool: if isinstance(expr, OrExpression) and parent_type == AndExpression: return True # AndExpression or OrExpression needs parentheses when inside NotExpression - if isinstance(expr, (AndExpression, OrExpression)) and parent_type == NotExpression: - return True - return False + return ( + isinstance(expr, (AndExpression, OrExpression)) and parent_type == NotExpression + ) def _is_simple_expression(expr: SearchExpression) -> bool: @@ -412,15 +410,15 @@ def _expression_to_string(expr: SearchExpression, parent_type: type = None) -> s raise ValueError(f"Unknown expression type: {type(expr)}") -def expression_to_string(expr: Optional[SearchExpression]) -> str: +def expression_to_string(expr: SearchExpression | None) -> str: if expr is None: return "" return _expression_to_string(expr) def _strip_tag_from_expression( - expr: Optional[SearchExpression], tag_name: str, enable_lax_search: bool = False -) -> Optional[SearchExpression]: + expr: SearchExpression | None, tag_name: str, enable_lax_search: bool = False +) -> SearchExpression | None: if expr is None: return None @@ -511,8 +509,8 @@ def strip_tag_from_query( def _extract_tag_names_from_expression( - expr: Optional[SearchExpression], enable_lax_search: bool = False -) -> List[str]: + expr: SearchExpression | None, enable_lax_search: bool = False +) -> list[str]: if expr is None: return [] @@ -546,7 +544,7 @@ def _extract_tag_names_from_expression( def extract_tag_names_from_query( query: str, user_profile: UserProfile | None = None -) -> List[str]: +) -> list[str]: try: ast = parse_search_query(query) except SearchQueryParseError: diff --git a/bookmarks/services/singlefile.py b/bookmarks/services/singlefile.py index 4fc2e9d..8b4ab3d 100644 --- a/bookmarks/services/singlefile.py +++ b/bookmarks/services/singlefile.py @@ -38,12 +38,12 @@ def create_snapshot(url: str, filepath: str): ) process.terminate() process.wait(timeout=20) - raise SingleFileError("Timeout expired while creating snapshot") + raise SingleFileError("Timeout expired while creating snapshot") from None except subprocess.TimeoutExpired: # Kill the whole process group, which should also clean up any chromium # processes spawned by single-file logger.error("Timeout expired while terminating. Killing process...") os.killpg(os.getpgid(process.pid), signal.SIGTERM) - raise SingleFileError("Timeout expired while creating snapshot") + raise SingleFileError("Timeout expired while creating snapshot") from None except subprocess.CalledProcessError as error: - raise SingleFileError(f"Failed to create snapshot: {error.stderr}") + raise SingleFileError(f"Failed to create snapshot: {error.stderr}") from error diff --git a/bookmarks/services/tags.py b/bookmarks/services/tags.py index 9d1cee1..4a129ed 100644 --- a/bookmarks/services/tags.py +++ b/bookmarks/services/tags.py @@ -1,6 +1,5 @@ import logging import operator -from typing import List from django.contrib.auth.models import User from django.utils import timezone @@ -11,7 +10,7 @@ from bookmarks.utils import unique logger = logging.getLogger(__name__) -def get_or_create_tags(tag_names: List[str], user: User): +def get_or_create_tags(tag_names: list[str], user: User): tags = [get_or_create_tag(tag_name, user) for tag_name in tag_names] return unique(tags, operator.attrgetter("id")) @@ -28,10 +27,10 @@ def get_or_create_tag(name: str, user: User): # Legacy databases might contain duplicate tags with different capitalization first_tag = Tag.objects.filter(name__iexact=name, owner=user).first() message = ( - "Found multiple tags for the name '{0}' with different capitalization. " - "Using the first tag with the name '{1}'. " + f"Found multiple tags for the name '{name}' with different capitalization. " + f"Using the first tag with the name '{first_tag.name}'. " "Since v.1.2 tags work case-insensitive, which means duplicates of the same name are not allowed anymore. " "To solve this error remove the duplicate tag in admin." - ).format(name, first_tag.name) + ) logger.error(message) return first_tag diff --git a/bookmarks/services/tasks.py b/bookmarks/services/tasks.py index 9c5ce61..f283f95 100644 --- a/bookmarks/services/tasks.py +++ b/bookmarks/services/tasks.py @@ -1,6 +1,5 @@ import functools import logging -from typing import List import waybackpy from django.conf import settings @@ -10,7 +9,7 @@ from django.utils import timezone from huey import crontab from huey.contrib.djhuey import HUEY as huey from huey.exceptions import TaskLockedException -from waybackpy.exceptions import WaybackError, TooManyRequestsError +from waybackpy.exceptions import TooManyRequestsError, WaybackError from bookmarks.models import Bookmark, BookmarkAsset, UserProfile from bookmarks.services import assets, favicon_loader, preview_image_loader @@ -263,7 +262,7 @@ def create_html_snapshot(bookmark: Bookmark): asset.save() -def create_html_snapshots(bookmark_list: List[Bookmark]): +def create_html_snapshots(bookmark_list: list[Bookmark]): if not is_html_snapshot_feature_active(): return diff --git a/bookmarks/services/website_loader.py b/bookmarks/services/website_loader.py index e396f57..a3e8c36 100644 --- a/bookmarks/services/website_loader.py +++ b/bookmarks/services/website_loader.py @@ -81,10 +81,12 @@ def _load_website_metadata(url: str): end = timezone.now() logger.debug(f"Parsing duration: {end - start}") - finally: - return WebsiteMetadata( - url=url, title=title, description=description, preview_image=preview_image - ) + except Exception: + pass + + return WebsiteMetadata( + url=url, title=title, description=description, preview_image=preview_image + ) CHUNK_SIZE = 50 * 1024 @@ -101,15 +103,12 @@ def load_page(url: str): for chunk in r.iter_content(chunk_size=CHUNK_SIZE): size += len(chunk) iteration = iteration + 1 - if content is None: - content = chunk - else: - content = content + chunk + content = chunk if content is None else content + chunk logger.debug(f"Loaded chunk (iteration={iteration}, total={size / 1024})") # Stop reading if we have parsed end of head tag - end_of_head = "".encode("utf-8") + end_of_head = b"" if end_of_head in content: logger.debug(f"Found closing head tag after {size} bytes") content = content.split(end_of_head)[0] + end_of_head diff --git a/bookmarks/settings/__init__.py b/bookmarks/settings/__init__.py index 4b2240f..655470b 100644 --- a/bookmarks/settings/__init__.py +++ b/bookmarks/settings/__init__.py @@ -1,4 +1,5 @@ # Use dev settings as default, use production if dev settings do not exist +# ruff: noqa try: from .dev import * except: diff --git a/bookmarks/settings/dev.py b/bookmarks/settings/dev.py index a4ed087..8f138c3 100644 --- a/bookmarks/settings/dev.py +++ b/bookmarks/settings/dev.py @@ -2,6 +2,8 @@ Development settings for linkding webapp """ +# ruff: noqa + # Start from development settings # noinspection PyUnresolvedReferences from .base import * diff --git a/bookmarks/settings/prod.py b/bookmarks/settings/prod.py index 48f36e9..4e54e6b 100644 --- a/bookmarks/settings/prod.py +++ b/bookmarks/settings/prod.py @@ -2,6 +2,8 @@ Production settings for linkding webapp """ +# ruff: noqa + # Start from development settings # noinspection PyUnresolvedReferences import os diff --git a/bookmarks/tasks.py b/bookmarks/tasks.py index 8fcc442..e62eff8 100644 --- a/bookmarks/tasks.py +++ b/bookmarks/tasks.py @@ -1,2 +1,3 @@ # Expose task modules to Huey Django extension -import bookmarks.services.tasks +# noinspection PyUnusedImports +import bookmarks.services.tasks # noqa: F401 diff --git a/bookmarks/templatetags/shared.py b/bookmarks/templatetags/shared.py index c8a56d4..63b2464 100644 --- a/bookmarks/templatetags/shared.py +++ b/bookmarks/templatetags/shared.py @@ -2,12 +2,11 @@ import re import bleach import markdown -from bleach_allowlist import markdown_tags, markdown_attrs +from bleach_allowlist import markdown_attrs, markdown_tags from django import template from django.utils.safestring import mark_safe from bookmarks import utils -from bookmarks.models import UserProfile register = template.Library() @@ -82,7 +81,7 @@ class HtmlMinNode(template.Node): def render_markdown(context, markdown_text): # naive approach to reusing the renderer for a single request # works for bookmark list for now - if not ("markdown_renderer" in context): + if "markdown_renderer" not in context: renderer = markdown.Markdown(extensions=["fenced_code", "nl2br"]) context["markdown_renderer"] = renderer else: diff --git a/bookmarks/tests/helpers.py b/bookmarks/tests/helpers.py index d2715bc..61e2df3 100644 --- a/bookmarks/tests/helpers.py +++ b/bookmarks/tests/helpers.py @@ -5,7 +5,6 @@ import random import shutil import tempfile from datetime import datetime -from typing import List from unittest import TestCase from bs4 import BeautifulSoup @@ -307,7 +306,7 @@ class HtmlTestMixin: class BookmarkListTestMixin(TestCase, HtmlTestMixin): def assertVisibleBookmarks( - self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + self, response, bookmarks: list[Bookmark], link_target: str = "_blank" ): soup = self.make_soup(response.content.decode()) bookmark_list = soup.select_one( @@ -325,7 +324,7 @@ class BookmarkListTestMixin(TestCase, HtmlTestMixin): self.assertIsNotNone(bookmark_item) def assertInvisibleBookmarks( - self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + self, response, bookmarks: list[Bookmark], link_target: str = "_blank" ): soup = self.make_soup(response.content.decode()) @@ -337,7 +336,7 @@ class BookmarkListTestMixin(TestCase, HtmlTestMixin): class TagCloudTestMixin(TestCase, HtmlTestMixin): - def assertVisibleTags(self, response, tags: List[Tag]): + def assertVisibleTags(self, response, tags: list[Tag]): soup = self.make_soup(response.content.decode()) tag_cloud = soup.select_one("div.tag-cloud") self.assertIsNotNone(tag_cloud) @@ -350,7 +349,7 @@ class TagCloudTestMixin(TestCase, HtmlTestMixin): for tag in tags: self.assertTrue(tag.name in tag_item_names) - def assertInvisibleTags(self, response, tags: List[Tag]): + def assertInvisibleTags(self, response, tags: list[Tag]): soup = self.make_soup(response.content.decode()) tag_items = soup.select("a[data-is-tag-item]") @@ -359,7 +358,7 @@ class TagCloudTestMixin(TestCase, HtmlTestMixin): for tag in tags: self.assertFalse(tag.name in tag_item_names) - def assertSelectedTags(self, response, tags: List[Tag]): + def assertSelectedTags(self, response, tags: list[Tag]): soup = self.make_soup(response.content.decode()) selected_tags = soup.select_one("p.selected-tags") self.assertIsNotNone(selected_tags) @@ -433,18 +432,18 @@ class ImportTestMixin: def render_tag(self, tag: BookmarkHtmlTag): return f"""