From 3b26190df5fe6ce11e79e452feda29dc718e19cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Sun, 4 Jan 2026 12:13:48 +0100 Subject: [PATCH] Format and lint with ruff (#1263) --- Makefile | 5 +- README.md | 10 +- bookmarks/admin.py | 17 ++-- bookmarks/api/auth.py | 2 +- bookmarks/api/routes.py | 16 ++-- bookmarks/api/serializers.py | 10 +- bookmarks/apps.py | 3 +- bookmarks/feeds.py | 5 +- bookmarks/forms.py | 8 +- bookmarks/management/commands/backup.py | 4 +- .../commands/create_initial_superuser.py | 4 +- .../management/commands/ensure_superuser.py | 2 +- bookmarks/management/commands/full_backup.py | 4 +- .../commands/generate_secret_key.py | 5 +- .../management/commands/migrate_tasks.py | 2 +- bookmarks/middlewares.py | 4 +- bookmarks/migrations/0001_initial.py | 3 +- .../migrations/0002_auto_20190629_2303.py | 3 +- .../migrations/0003_auto_20200913_0656.py | 1 - .../migrations/0004_auto_20200926_1028.py | 1 - .../migrations/0005_auto_20210103_1212.py | 4 +- .../migrations/0006_bookmark_is_archived.py | 1 - bookmarks/migrations/0007_userprofile.py | 2 +- .../0008_userprofile_bookmark_date_display.py | 1 - .../0009_bookmark_web_archive_snapshot_url.py | 1 - .../0010_userprofile_bookmark_link_target.py | 1 - ...011_userprofile_web_archive_integration.py | 1 - bookmarks/migrations/0012_toast.py | 3 +- .../0013_web_archive_optin_toast.py | 2 +- bookmarks/migrations/0015_feedtoken.py | 3 +- bookmarks/migrations/0016_bookmark_shared.py | 1 - .../0017_userprofile_enable_sharing.py | 1 - .../migrations/0018_bookmark_favicon_file.py | 1 - .../0019_userprofile_enable_favicons.py | 1 - .../migrations/0020_userprofile_tag_search.py | 1 - .../0021_userprofile_display_url.py | 1 - bookmarks/migrations/0022_bookmark_notes.py | 1 - .../0023_userprofile_permanent_notes.py | 1 - .../0024_userprofile_enable_public_sharing.py | 1 - .../0025_userprofile_search_preferences.py | 1 - .../migrations/0026_userprofile_custom_css.py | 1 - ...e_bookmark_description_display_and_more.py | 1 - ...isplay_archive_bookmark_action_and_more.py | 1 - .../0029_bookmark_list_actions_toast.py | 4 +- bookmarks/migrations/0030_bookmarkasset.py | 1 - ...profile_enable_automatic_html_snapshots.py | 1 - .../0032_html_snapshots_hint_toast.py | 4 +- .../0033_userprofile_default_mark_unread.py | 1 - ...34_bookmark_preview_image_file_and_more.py | 1 - .../0035_userprofile_tag_grouping.py | 1 - .../0036_userprofile_auto_tagging_rules.py | 1 - bookmarks/migrations/0037_globalsettings.py | 1 - .../0038_globalsettings_guest_profile_user.py | 1 - ...039_globalsettings_enable_link_prefetch.py | 1 - ...040_userprofile_items_per_page_and_more.py | 1 - bookmarks/migrations/0041_merge_metadata.py | 7 +- .../0042_userprofile_custom_css_hash.py | 1 - .../0043_userprofile_collapse_side_panel.py | 1 - .../0044_bookmark_latest_snapshot.py | 1 - ...userprofile_hide_bundles_bookmarkbundle.py | 1 - .../0046_add_url_normalized_field.py | 1 - .../0047_populate_url_normalized_field.py | 2 +- .../0048_userprofile_default_mark_shared.py | 1 - .../0049_userprofile_legacy_search.py | 1 - bookmarks/migrations/0050_new_search_toast.py | 2 - .../migrations/0051_fix_normalized_url.py | 2 +- bookmarks/migrations/0052_apitoken.py | 1 - .../migrations/0053_migrate_api_tokens.py | 1 - bookmarks/models.py | 17 ++-- bookmarks/queries.py | 36 ++++--- bookmarks/services/assets.py | 2 +- bookmarks/services/auto_tagging.py | 3 +- bookmarks/services/bookmarks.py | 31 +++--- bookmarks/services/exporter.py | 5 +- bookmarks/services/importer.py | 21 ++-- bookmarks/services/monolith.py | 4 +- bookmarks/services/parser.py | 14 ++- bookmarks/services/preview_image_loader.py | 3 +- bookmarks/services/search_query_parser.py | 28 +++--- bookmarks/services/singlefile.py | 6 +- bookmarks/services/tags.py | 9 +- bookmarks/services/tasks.py | 5 +- bookmarks/services/website_loader.py | 17 ++-- bookmarks/settings/__init__.py | 1 + bookmarks/settings/dev.py | 2 + bookmarks/settings/prod.py | 2 + bookmarks/tasks.py | 3 +- bookmarks/templatetags/shared.py | 5 +- bookmarks/tests/helpers.py | 25 +++-- bookmarks/tests/test_assets_service.py | 48 ++++------ bookmarks/tests/test_auth_api.py | 3 +- bookmarks/tests/test_auth_proxy_support.py | 5 +- bookmarks/tests/test_auto_tagging.py | 3 +- bookmarks/tests/test_bookmark_action_view.py | 1 - .../tests/test_bookmark_archived_view.py | 9 +- ...test_bookmark_archived_view_performance.py | 1 - bookmarks/tests/test_bookmark_assets_api.py | 2 +- .../tests/test_bookmark_details_modal.py | 4 +- bookmarks/tests/test_bookmark_edit_view.py | 1 - bookmarks/tests/test_bookmark_index_view.py | 8 +- .../test_bookmark_index_view_performance.py | 1 - bookmarks/tests/test_bookmark_new_view.py | 1 - bookmarks/tests/test_bookmark_search_tag.py | 4 +- bookmarks/tests/test_bookmark_shared_view.py | 7 +- .../test_bookmark_shared_view_performance.py | 1 - bookmarks/tests/test_bookmark_validation.py | 1 - bookmarks/tests/test_bookmarks_api.py | 15 ++- .../tests/test_bookmarks_api_performance.py | 3 +- .../tests/test_bookmarks_api_permissions.py | 2 +- .../tests/test_bookmarks_list_template.py | 18 ++-- bookmarks/tests/test_bookmarks_model.py | 1 - bookmarks/tests/test_bookmarks_service.py | 24 +++-- bookmarks/tests/test_bookmarks_tasks.py | 1 - bookmarks/tests/test_bundles_api.py | 2 +- bookmarks/tests/test_bundles_edit_view.py | 1 - bookmarks/tests/test_bundles_index_view.py | 3 +- bookmarks/tests/test_bundles_new_view.py | 4 +- bookmarks/tests/test_bundles_preview_view.py | 1 - bookmarks/tests/test_context_path.py | 1 - .../test_create_initial_superuser_command.py | 3 +- bookmarks/tests/test_exporter.py | 34 +++---- bookmarks/tests/test_exporter_performance.py | 3 +- bookmarks/tests/test_favicon_loader.py | 2 +- bookmarks/tests/test_feeds.py | 21 ++-- bookmarks/tests/test_feeds_performance.py | 1 - bookmarks/tests/test_health_view.py | 1 - bookmarks/tests/test_importer.py | 16 ++-- bookmarks/tests/test_layout.py | 9 +- bookmarks/tests/test_linkding_middleware.py | 4 +- bookmarks/tests/test_login_view.py | 3 +- bookmarks/tests/test_metadata_view.py | 1 - bookmarks/tests/test_monolith_service.py | 2 +- bookmarks/tests/test_opensearch_view.py | 1 - bookmarks/tests/test_pagination_tag.py | 39 ++++---- bookmarks/tests/test_parser.py | 9 +- bookmarks/tests/test_preview_image_loader.py | 4 +- bookmarks/tests/test_queries.py | 91 ++++++++---------- bookmarks/tests/test_search_query_parser.py | 32 +++---- bookmarks/tests/test_settings_export_view.py | 5 +- bookmarks/tests/test_settings_general_view.py | 11 +-- bookmarks/tests/test_settings_import_view.py | 5 +- .../tests/test_settings_integrations_view.py | 3 +- bookmarks/tests/test_singlefile_service.py | 8 +- bookmarks/tests/test_tag_cloud_template.py | 12 +-- bookmarks/tests/test_tags_index_view.py | 2 +- bookmarks/tests/test_tags_merge_view.py | 4 +- bookmarks/tests/test_tags_model.py | 1 - bookmarks/tests/test_tags_service.py | 1 - bookmarks/tests/test_toasts_view.py | 5 +- bookmarks/tests/test_user_profile_model.py | 1 - bookmarks/tests/test_user_select_tag.py | 10 +- bookmarks/tests/test_utils.py | 3 +- bookmarks/tests/test_website_loader.py | 7 +- .../e2e_test_bookmark_details_modal.py | 2 +- .../e2e_test_bookmark_page_bulk_edit.py | 2 +- .../e2e_test_bookmark_page_partial_updates.py | 6 +- .../tests_e2e/e2e_test_bundle_preview.py | 8 +- .../tests_e2e/e2e_test_collapse_side_panel.py | 1 - .../tests_e2e/e2e_test_edit_bookmark_form.py | 3 +- .../tests_e2e/e2e_test_new_bookmark_form.py | 1 - .../tests_e2e/e2e_test_settings_general.py | 2 +- bookmarks/tests_e2e/helpers.py | 3 +- bookmarks/type_defs.py | 2 +- bookmarks/urls.py | 95 +++++++++++-------- bookmarks/utils.py | 15 ++- bookmarks/views/__init__.py | 12 --- bookmarks/views/access.py | 14 +-- bookmarks/views/assets.py | 2 +- bookmarks/views/bookmarks.py | 61 ++++++------ bookmarks/views/bundles.py | 19 ++-- bookmarks/views/contexts.py | 18 ++-- bookmarks/views/manifest.py | 2 +- bookmarks/views/opensearch.py | 2 +- bookmarks/views/settings.py | 11 +-- bookmarks/views/tags.py | 2 +- pyproject.toml | 20 +++- scripts/generate-changelog.py | 3 +- uv.lock | 84 ++++++---------- 178 files changed, 601 insertions(+), 739 deletions(-) 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"""
- - {tag.title if tag.title else ''} + {tag.title if tag.title else ""} - {f'
{tag.description}' if tag.description else ''} + {f"
{tag.description}" if tag.description else ""} """ - def render_html(self, tags: List[BookmarkHtmlTag] = None, tags_html: str = ""): + def render_html(self, tags: list[BookmarkHtmlTag] = None, tags_html: str = ""): if tags: rendered_tags = [self.render_tag(tag) for tag in tags] tags_html = "\n".join(rendered_tags) diff --git a/bookmarks/tests/test_assets_service.py b/bookmarks/tests/test_assets_service.py index 5eb3ccc..8bddd27 100644 --- a/bookmarks/tests/test_assets_service.py +++ b/bookmarks/tests/test_assets_service.py @@ -2,6 +2,7 @@ import datetime import gzip import os from datetime import timedelta +from pathlib import Path from unittest import mock from django.core.files.uploadedfile import SimpleUploadedFile @@ -14,7 +15,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: self.setup_temp_assets_dir() self.get_or_create_test_user() @@ -27,7 +27,7 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): self.mock_singlefile_create_snapshot_patcher.start() ) self.mock_singlefile_create_snapshot.side_effect = lambda url, filepath: ( - open(filepath, "w").write(self.html_content) + Path(filepath).write_text(self.html_content) ) def tearDown(self) -> None: @@ -55,16 +55,14 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertIsNone(asset.id) def test_create_snapshot(self): - initial_modified = timezone.datetime( - 2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + initial_modified = timezone.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) bookmark = self.setup_bookmark( url="https://example.com", modified=initial_modified ) asset = assets.create_snapshot_asset(bookmark) asset.save() asset.date_created = timezone.datetime( - 2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc + 2023, 8, 11, 21, 45, 11, tzinfo=datetime.UTC ) assets.create_snapshot(asset) @@ -104,9 +102,11 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): asset = assets.create_snapshot_asset(bookmark) asset.save() - self.mock_singlefile_create_snapshot.side_effect = Exception + self.mock_singlefile_create_snapshot.side_effect = RuntimeError( + "Snapshot failed" + ) - with self.assertRaises(Exception): + with self.assertRaises(RuntimeError): assets.create_snapshot(asset) asset.refresh_from_db() @@ -128,9 +128,7 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(saved_file.endswith("aaaa.html.gz")) def test_upload_snapshot(self): - initial_modified = timezone.datetime( - 2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + initial_modified = timezone.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) bookmark = self.setup_bookmark( url="https://example.com", modified=initial_modified ) @@ -167,9 +165,9 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): # make gzip.open raise an exception with mock.patch("gzip.open") as mock_gzip_open: - mock_gzip_open.side_effect = Exception + mock_gzip_open.side_effect = OSError("File operation failed") - with self.assertRaises(Exception): + with self.assertRaises(OSError): assets.upload_snapshot(bookmark, b"invalid content") # asset is not saved to the database @@ -190,9 +188,7 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): @disable_logging def test_upload_asset(self): - initial_modified = timezone.datetime( - 2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + initial_modified = timezone.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) bookmark = self.setup_bookmark(modified=initial_modified) file_content = b"test content" upload_file = SimpleUploadedFile( @@ -229,9 +225,7 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): @disable_logging def test_upload_gzip_asset(self): - initial_modified = timezone.datetime( - 2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + initial_modified = timezone.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) bookmark = self.setup_bookmark(modified=initial_modified) file_content = gzip.compress(b"test content") upload_file = SimpleUploadedFile( @@ -292,9 +286,9 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): # make open raise an exception with mock.patch("builtins.open") as mock_open: - mock_open.side_effect = Exception + mock_open.side_effect = OSError("File operation failed") - with self.assertRaises(Exception): + with self.assertRaises(OSError): assets.upload_asset(bookmark, upload_file) # asset is not saved to the database @@ -344,12 +338,12 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): failing_asset.save() # Make the snapshot creation fail - self.mock_singlefile_create_snapshot.side_effect = Exception( + self.mock_singlefile_create_snapshot.side_effect = RuntimeError( "Snapshot creation failed" ) # Attempt to create a snapshot (which will fail) - with self.assertRaises(Exception): + with self.assertRaises(RuntimeError): assets.create_snapshot(failing_asset) # Verify that the bookmark's latest_snapshot is still the initial snapshot @@ -365,10 +359,10 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): # Make the gzip.open function fail with mock.patch("gzip.open") as mock_gzip_open: - mock_gzip_open.side_effect = Exception("Upload failed") + mock_gzip_open.side_effect = OSError("Upload failed") # Attempt to upload a snapshot (which will fail) - with self.assertRaises(Exception): + with self.assertRaises(OSError): assets.upload_snapshot(bookmark, b"New content") # Verify that the bookmark's latest_snapshot is still the initial snapshot @@ -474,9 +468,7 @@ class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): @disable_logging def test_remove_asset(self): - initial_modified = timezone.datetime( - 2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + initial_modified = timezone.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) bookmark = self.setup_bookmark(modified=initial_modified) file_content = b"test content for removal" upload_file = SimpleUploadedFile( diff --git a/bookmarks/tests/test_auth_api.py b/bookmarks/tests/test_auth_api.py index cf96132..40931ac 100644 --- a/bookmarks/tests/test_auth_api.py +++ b/bookmarks/tests/test_auth_api.py @@ -1,11 +1,10 @@ from django.urls import reverse from rest_framework import status -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase class AuthApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): - def authenticate(self, keyword): self.api_token = self.setup_api_token() self.client.credentials(HTTP_AUTHORIZATION=f"{keyword} {self.api_token.key}") diff --git a/bookmarks/tests/test_auth_proxy_support.py b/bookmarks/tests/test_auth_proxy_support.py index 933ccf6..efb8dce 100644 --- a/bookmarks/tests/test_auth_proxy_support.py +++ b/bookmarks/tests/test_auth_proxy_support.py @@ -1,9 +1,10 @@ -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch from django.test import TestCase, modify_settings from django.urls import reverse -from bookmarks.models import User + from bookmarks.middlewares import CustomRemoteUserMiddleware +from bookmarks.models import User class AuthProxySupportTest(TestCase): diff --git a/bookmarks/tests/test_auto_tagging.py b/bookmarks/tests/test_auto_tagging.py index 1273264..8538d9f 100644 --- a/bookmarks/tests/test_auto_tagging.py +++ b/bookmarks/tests/test_auto_tagging.py @@ -1,6 +1,7 @@ -from bookmarks.services import auto_tagging from django.test import TestCase +from bookmarks.services import auto_tagging + class AutoTaggingTestCase(TestCase): def test_auto_tag_by_domain(self): diff --git a/bookmarks/tests/test_bookmark_action_view.py b/bookmarks/tests/test_bookmark_action_view.py index 29fd3a0..4f40a78 100644 --- a/bookmarks/tests/test_bookmark_action_view.py +++ b/bookmarks/tests/test_bookmark_action_view.py @@ -19,7 +19,6 @@ from bookmarks.tests.helpers import ( class BookmarkActionViewTestCase( TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin ): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_archived_view.py b/bookmarks/tests/test_bookmark_archived_view.py index 771c7fa..1fd98e5 100644 --- a/bookmarks/tests/test_bookmark_archived_view.py +++ b/bookmarks/tests/test_bookmark_archived_view.py @@ -15,7 +15,6 @@ from bookmarks.tests.helpers import ( class BookmarkArchivedViewTestCase( TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin ): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -305,7 +304,7 @@ class BookmarkArchivedViewTestCase( html = response.content.decode() self.assertInHTML( - f""" + """ @@ -351,7 +350,7 @@ class BookmarkArchivedViewTestCase( html = response.content.decode() self.assertInHTML( - f""" + """ diff --git a/bookmarks/tests/test_bookmark_archived_view_performance.py b/bookmarks/tests/test_bookmark_archived_view_performance.py index cc3784a..769b671 100644 --- a/bookmarks/tests/test_bookmark_archived_view_performance.py +++ b/bookmarks/tests/test_bookmark_archived_view_performance.py @@ -11,7 +11,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BookmarkArchivedViewPerformanceTestCase( TransactionTestCase, BookmarkFactoryMixin, HtmlTestMixin ): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_assets_api.py b/bookmarks/tests/test_bookmark_assets_api.py index 284e8dc..65cfa5a 100644 --- a/bookmarks/tests/test_bookmark_assets_api.py +++ b/bookmarks/tests/test_bookmark_assets_api.py @@ -6,7 +6,7 @@ from django.urls import reverse from rest_framework import status from bookmarks.models import BookmarkAsset -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase class BookmarkAssetsApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): diff --git a/bookmarks/tests/test_bookmark_details_modal.py b/bookmarks/tests/test_bookmark_details_modal.py index 141093c..4c1b3de 100644 --- a/bookmarks/tests/test_bookmark_details_modal.py +++ b/bookmarks/tests/test_bookmark_details_modal.py @@ -262,9 +262,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin self.assertIsNotNone(image) def test_internet_archive_link_with_fallback_url(self): - date_added = timezone.datetime( - 2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc - ) + date_added = timezone.datetime(2023, 8, 11, 21, 45, 11, tzinfo=datetime.UTC) bookmark = self.setup_bookmark(url="https://example.com/", added=date_added) fallback_web_archive_url = ( "https://web.archive.org/web/20230811214511/https://example.com/" diff --git a/bookmarks/tests/test_bookmark_edit_view.py b/bookmarks/tests/test_bookmark_edit_view.py index 7b442ab..5a6b3cb 100644 --- a/bookmarks/tests/test_bookmark_edit_view.py +++ b/bookmarks/tests/test_bookmark_edit_view.py @@ -7,7 +7,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_index_view.py b/bookmarks/tests/test_bookmark_index_view.py index b44155e..9d90dbe 100644 --- a/bookmarks/tests/test_bookmark_index_view.py +++ b/bookmarks/tests/test_bookmark_index_view.py @@ -299,7 +299,7 @@ class BookmarkIndexViewTestCase( html = response.content.decode() self.assertInHTML( - f""" + """ @@ -345,7 +345,7 @@ class BookmarkIndexViewTestCase( html = response.content.decode() self.assertInHTML( - f""" + """ diff --git a/bookmarks/tests/test_bookmark_index_view_performance.py b/bookmarks/tests/test_bookmark_index_view_performance.py index 3e16fe3..ae12e8c 100644 --- a/bookmarks/tests/test_bookmark_index_view_performance.py +++ b/bookmarks/tests/test_bookmark_index_view_performance.py @@ -11,7 +11,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BookmarkIndexViewPerformanceTestCase( TransactionTestCase, BookmarkFactoryMixin, HtmlTestMixin ): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_new_view.py b/bookmarks/tests/test_bookmark_new_view.py index e4a8fb0..7cfa55f 100644 --- a/bookmarks/tests/test_bookmark_new_view.py +++ b/bookmarks/tests/test_bookmark_new_view.py @@ -6,7 +6,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_search_tag.py b/bookmarks/tests/test_bookmark_search_tag.py index 8cde66a..21bc9a2 100644 --- a/bookmarks/tests/test_bookmark_search_tag.py +++ b/bookmarks/tests/test_bookmark_search_tag.py @@ -1,6 +1,6 @@ from bs4 import BeautifulSoup -from django.template import Template, RequestContext -from django.test import TestCase, RequestFactory +from django.template import RequestContext, Template +from django.test import RequestFactory, TestCase from bookmarks.models import BookmarkSearch from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin diff --git a/bookmarks/tests/test_bookmark_shared_view.py b/bookmarks/tests/test_bookmark_shared_view.py index a3f2680..e5989da 100644 --- a/bookmarks/tests/test_bookmark_shared_view.py +++ b/bookmarks/tests/test_bookmark_shared_view.py @@ -1,11 +1,10 @@ import urllib.parse -from typing import List from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse -from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile +from bookmarks.models import Bookmark, BookmarkSearch, UserProfile from bookmarks.tests.helpers import ( BookmarkFactoryMixin, BookmarkListTestMixin, @@ -29,7 +28,7 @@ class BookmarkSharedViewTestCase( count=count, ) - def assertVisibleUserOptions(self, response, users: List[User]): + def assertVisibleUserOptions(self, response, users: list[User]): html = response.content.decode() user_options = [''] @@ -39,7 +38,7 @@ class BookmarkSharedViewTestCase( ) user_select_html = f""" """ diff --git a/bookmarks/tests/test_bookmark_shared_view_performance.py b/bookmarks/tests/test_bookmark_shared_view_performance.py index 748c9ad..20e97b4 100644 --- a/bookmarks/tests/test_bookmark_shared_view_performance.py +++ b/bookmarks/tests/test_bookmark_shared_view_performance.py @@ -11,7 +11,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BookmarkSharedViewPerformanceTestCase( TransactionTestCase, BookmarkFactoryMixin, HtmlTestMixin ): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bookmark_validation.py b/bookmarks/tests/test_bookmark_validation.py index d965a13..453080a 100644 --- a/bookmarks/tests/test_bookmark_validation.py +++ b/bookmarks/tests/test_bookmark_validation.py @@ -30,7 +30,6 @@ DISABLED_URL_VALIDATION_TEST_CASES = [ class BookmarkValidationTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: self.get_or_create_test_user() diff --git a/bookmarks/tests/test_bookmarks_api.py b/bookmarks/tests/test_bookmarks_api.py index 749c10b..79c4b12 100644 --- a/bookmarks/tests/test_bookmarks_api.py +++ b/bookmarks/tests/test_bookmarks_api.py @@ -2,7 +2,7 @@ import datetime import io import urllib.parse from collections import OrderedDict -from unittest.mock import patch, ANY +from unittest.mock import ANY, patch from django.contrib.auth.models import User from django.test import override_settings @@ -16,12 +16,11 @@ from bookmarks.models import Bookmark, BookmarkSearch, UserProfile from bookmarks.services import website_loader from bookmarks.services.wayback import generate_fallback_webarchive_url from bookmarks.services.website_loader import WebsiteMetadata -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase from bookmarks.utils import app_version class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): - def setUp(self): self.mock_assets_upload_snapshot_patcher = patch( "bookmarks.services.assets.upload_snapshot", @@ -359,9 +358,9 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): self.setup_bookmark(title="searchvalue", shared=True, user=user2), self.setup_bookmark(title="searchvalue", shared=True, user=user3), ] - self.setup_bookmark(shared=True, user=user1), - self.setup_bookmark(shared=True, user=user2), - self.setup_bookmark(shared=True, user=user3), + self.setup_bookmark(shared=True, user=user1) + self.setup_bookmark(shared=True, user=user2) + self.setup_bookmark(shared=True, user=user3) response = self.get( reverse("linkding:bookmark-shared") + "?q=searchvalue", @@ -675,9 +674,7 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark( web_archive_snapshot_url="", url="https://example.com/", - added=timezone.datetime( - 2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc - ), + added=timezone.datetime(2023, 8, 11, 21, 45, 11, tzinfo=datetime.UTC), ) url = reverse("linkding:bookmark-detail", args=[bookmark.id]) diff --git a/bookmarks/tests/test_bookmarks_api_performance.py b/bookmarks/tests/test_bookmarks_api_performance.py index 3fb25d0..d89e2d7 100644 --- a/bookmarks/tests/test_bookmarks_api_performance.py +++ b/bookmarks/tests/test_bookmarks_api_performance.py @@ -5,11 +5,10 @@ from django.urls import reverse from rest_framework import status from bookmarks.models import GlobalSettings -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): - def setUp(self) -> None: self.api_token = self.setup_api_token() self.client.credentials(HTTP_AUTHORIZATION="Token " + self.api_token.key) diff --git a/bookmarks/tests/test_bookmarks_api_permissions.py b/bookmarks/tests/test_bookmarks_api_permissions.py index 83ea958..013262c 100644 --- a/bookmarks/tests/test_bookmarks_api_permissions.py +++ b/bookmarks/tests/test_bookmarks_api_permissions.py @@ -3,7 +3,7 @@ import urllib.parse from django.urls import reverse from rest_framework import status -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): diff --git a/bookmarks/tests/test_bookmarks_list_template.py b/bookmarks/tests/test_bookmarks_list_template.py index eb961ef..17cfc95 100644 --- a/bookmarks/tests/test_bookmarks_list_template.py +++ b/bookmarks/tests/test_bookmarks_list_template.py @@ -1,23 +1,21 @@ import datetime -from typing import Type from dateutil.relativedelta import relativedelta from django.contrib.auth.models import AnonymousUser from django.http import HttpResponse -from django.template import Template, RequestContext -from django.test import TestCase, RequestFactory +from django.template import RequestContext, Template +from django.test import RequestFactory, TestCase from django.urls import reverse -from django.utils import timezone, formats +from django.utils import formats, timezone from bookmarks.middlewares import LinkdingMiddleware -from bookmarks.models import Bookmark, BookmarkSearch, UserProfile, User +from bookmarks.models import Bookmark, BookmarkSearch, User, UserProfile from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin -from bookmarks.views import contexts from bookmarks.utils import app_version +from bookmarks.views import contexts class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def assertBookmarksLink( self, html: str, bookmark: Bookmark, link_target: str = "_blank" ): @@ -262,7 +260,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def render_template( self, url="/bookmarks", - context_type: Type[ + context_type: type[ contexts.BookmarkListContext ] = contexts.ActiveBookmarkListContext, user: User | AnonymousUser = None, @@ -524,9 +522,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): user.profile.bookmark_date_display = UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE user.profile.save() - date_added = timezone.datetime( - 2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc - ) + date_added = timezone.datetime(2023, 8, 11, 21, 45, 11, tzinfo=datetime.UTC) bookmark = self.setup_bookmark( url="https://example.com/article", added=date_added ) diff --git a/bookmarks/tests/test_bookmarks_model.py b/bookmarks/tests/test_bookmarks_model.py index 0331ce0..1dcbe15 100644 --- a/bookmarks/tests/test_bookmarks_model.py +++ b/bookmarks/tests/test_bookmarks_model.py @@ -4,7 +4,6 @@ from bookmarks.models import Bookmark class BookmarkTestCase(TestCase): - def test_bookmark_resolved_title(self): bookmark = Bookmark( title="Custom title", diff --git a/bookmarks/tests/test_bookmarks_service.py b/bookmarks/tests/test_bookmarks_service.py index 47c95c3..47878c8 100644 --- a/bookmarks/tests/test_bookmarks_service.py +++ b/bookmarks/tests/test_bookmarks_service.py @@ -4,31 +4,29 @@ from django.test import TestCase from django.utils import timezone from bookmarks.models import Bookmark, Tag -from bookmarks.services import tasks -from bookmarks.services import website_loader +from bookmarks.services import tasks, website_loader from bookmarks.services.bookmarks import ( - create_bookmark, - update_bookmark, archive_bookmark, archive_bookmarks, - unarchive_bookmark, - unarchive_bookmarks, + create_bookmark, + create_html_snapshots, delete_bookmarks, - tag_bookmarks, - untag_bookmarks, + enhance_with_website_metadata, mark_bookmarks_as_read, mark_bookmarks_as_unread, - share_bookmarks, - unshare_bookmarks, - enhance_with_website_metadata, refresh_bookmarks_metadata, - create_html_snapshots, + share_bookmarks, + tag_bookmarks, + unarchive_bookmark, + unarchive_bookmarks, + unshare_bookmarks, + untag_bookmarks, + update_bookmark, ) from bookmarks.tests.helpers import BookmarkFactoryMixin class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: self.get_or_create_test_user() diff --git a/bookmarks/tests/test_bookmarks_tasks.py b/bookmarks/tests/test_bookmarks_tasks.py index b5f36e3..fd77004 100644 --- a/bookmarks/tests/test_bookmarks_tasks.py +++ b/bookmarks/tests/test_bookmarks_tasks.py @@ -25,7 +25,6 @@ def create_wayback_machine_save_api_mock( class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self): huey.immediate = True huey.results = True diff --git a/bookmarks/tests/test_bundles_api.py b/bookmarks/tests/test_bundles_api.py index 2446f27..07fc9fb 100644 --- a/bookmarks/tests/test_bundles_api.py +++ b/bookmarks/tests/test_bundles_api.py @@ -2,7 +2,7 @@ from django.urls import reverse from rest_framework import status from bookmarks.models import BookmarkBundle -from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, LinkdingApiTestCase class BundlesApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): diff --git a/bookmarks/tests/test_bundles_edit_view.py b/bookmarks/tests/test_bundles_edit_view.py index f28d59d..be4bc19 100644 --- a/bookmarks/tests/test_bundles_edit_view.py +++ b/bookmarks/tests/test_bundles_edit_view.py @@ -5,7 +5,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class BundleEditViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bundles_index_view.py b/bookmarks/tests/test_bundles_index_view.py index 6ce27d2..cdad1fd 100644 --- a/bookmarks/tests/test_bundles_index_view.py +++ b/bookmarks/tests/test_bundles_index_view.py @@ -7,7 +7,6 @@ from bookmarks.utils import app_version class BundleIndexViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -32,7 +31,7 @@ class BundleIndexViewTestCase(TestCase, BookmarkFactoryMixin): - { bundle.name } + {bundle.name} diff --git a/bookmarks/tests/test_bundles_new_view.py b/bookmarks/tests/test_bundles_new_view.py index f79680d..0bcb167 100644 --- a/bookmarks/tests/test_bundles_new_view.py +++ b/bookmarks/tests/test_bundles_new_view.py @@ -1,13 +1,13 @@ +from urllib.parse import urlencode + from django.test import TestCase from django.urls import reverse -from urllib.parse import urlencode from bookmarks.models import BookmarkBundle from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BundleNewViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_bundles_preview_view.py b/bookmarks/tests/test_bundles_preview_view.py index 4f03459..5b8454e 100644 --- a/bookmarks/tests/test_bundles_preview_view.py +++ b/bookmarks/tests/test_bundles_preview_view.py @@ -5,7 +5,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BundlePreviewViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_context_path.py b/bookmarks/tests/test_context_path.py index a137c09..f7a2565 100644 --- a/bookmarks/tests/test_context_path.py +++ b/bookmarks/tests/test_context_path.py @@ -10,7 +10,6 @@ class MockUrlConf: class ContextPathTestCase(TestCase): - def setUp(self): self.urls_module = importlib.import_module("bookmarks.urls") diff --git a/bookmarks/tests/test_create_initial_superuser_command.py b/bookmarks/tests/test_create_initial_superuser_command.py index 3dfb137..0021a5e 100644 --- a/bookmarks/tests/test_create_initial_superuser_command.py +++ b/bookmarks/tests/test_create_initial_superuser_command.py @@ -3,12 +3,11 @@ from unittest import mock from django.test import TestCase -from bookmarks.models import User from bookmarks.management.commands.create_initial_superuser import Command +from bookmarks.models import User class TestCreateInitialSuperuserCommand(TestCase): - @mock.patch.dict( os.environ, {"LD_SUPERUSER_NAME": "john", "LD_SUPERUSER_PASSWORD": "password123"}, diff --git a/bookmarks/tests/test_exporter.py b/bookmarks/tests/test_exporter.py index 1ef4e91..466595f 100644 --- a/bookmarks/tests/test_exporter.py +++ b/bookmarks/tests/test_exporter.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from django.test import TestCase @@ -12,15 +12,15 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark( url="https://example.com/1", title="Title 1", - added=datetime.fromtimestamp(1, timezone.utc), - modified=datetime.fromtimestamp(11, timezone.utc), + added=datetime.fromtimestamp(1, UTC), + modified=datetime.fromtimestamp(11, UTC), description="Example description", ), self.setup_bookmark( url="https://example.com/2", title="Title 2", - added=datetime.fromtimestamp(2, timezone.utc), - modified=datetime.fromtimestamp(22, timezone.utc), + added=datetime.fromtimestamp(2, UTC), + modified=datetime.fromtimestamp(22, UTC), tags=[ self.setup_tag(name="tag1"), self.setup_tag(name="tag2"), @@ -30,22 +30,22 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark( url="https://example.com/3", title="Title 3", - added=datetime.fromtimestamp(3, timezone.utc), - modified=datetime.fromtimestamp(33, timezone.utc), + added=datetime.fromtimestamp(3, UTC), + modified=datetime.fromtimestamp(33, UTC), unread=True, ), self.setup_bookmark( url="https://example.com/4", title="Title 4", - added=datetime.fromtimestamp(4, timezone.utc), - modified=datetime.fromtimestamp(44, timezone.utc), + added=datetime.fromtimestamp(4, UTC), + modified=datetime.fromtimestamp(44, UTC), shared=True, ), self.setup_bookmark( url="https://example.com/5", title="Title 5", - added=datetime.fromtimestamp(5, timezone.utc), - modified=datetime.fromtimestamp(55, timezone.utc), + added=datetime.fromtimestamp(5, UTC), + modified=datetime.fromtimestamp(55, UTC), shared=True, description="Example description", notes="Example notes", @@ -53,23 +53,23 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark( url="https://example.com/6", title="Title 6", - added=datetime.fromtimestamp(6, timezone.utc), - modified=datetime.fromtimestamp(66, timezone.utc), + added=datetime.fromtimestamp(6, UTC), + modified=datetime.fromtimestamp(66, UTC), shared=True, notes="Example notes", ), self.setup_bookmark( url="https://example.com/7", title="Title 7", - added=datetime.fromtimestamp(7, timezone.utc), - modified=datetime.fromtimestamp(77, timezone.utc), + added=datetime.fromtimestamp(7, UTC), + modified=datetime.fromtimestamp(77, UTC), is_archived=True, ), self.setup_bookmark( url="https://example.com/8", title="Title 8", - added=datetime.fromtimestamp(8, timezone.utc), - modified=datetime.fromtimestamp(88, timezone.utc), + added=datetime.fromtimestamp(8, UTC), + modified=datetime.fromtimestamp(88, UTC), tags=[self.setup_tag(name="tag4"), self.setup_tag(name="tag5")], is_archived=True, ), diff --git a/bookmarks/tests/test_exporter_performance.py b/bookmarks/tests/test_exporter_performance.py index 751b0c7..ef30e3e 100644 --- a/bookmarks/tests/test_exporter_performance.py +++ b/bookmarks/tests/test_exporter_performance.py @@ -8,7 +8,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class ExporterPerformanceTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -19,7 +18,7 @@ class ExporterPerformanceTestCase(TestCase, BookmarkFactoryMixin): def test_export_max_queries(self): # set up some bookmarks with associated tags num_initial_bookmarks = 10 - for index in range(num_initial_bookmarks): + for _i in range(num_initial_bookmarks): self.setup_bookmark(tags=[self.setup_tag()]) # capture number of queries diff --git a/bookmarks/tests/test_favicon_loader.py b/bookmarks/tests/test_favicon_loader.py index 6c7b4ce..bef18b9 100644 --- a/bookmarks/tests/test_favicon_loader.py +++ b/bookmarks/tests/test_favicon_loader.py @@ -1,7 +1,7 @@ import io import os.path -import time import tempfile +import time from pathlib import Path from unittest import mock diff --git a/bookmarks/tests/test_feeds.py b/bookmarks/tests/test_feeds.py index 4b02745..cc4233b 100644 --- a/bookmarks/tests/test_feeds.py +++ b/bookmarks/tests/test_feeds.py @@ -19,7 +19,6 @@ def rfc2822_date(date): class FeedsTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -257,12 +256,12 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin): self.assertContains(response, f"{bookmark2.url}", count=1) def test_unread_parameter(self): - self.setup_bookmark(unread=True), - self.setup_bookmark(unread=True), - self.setup_bookmark(unread=False), - self.setup_bookmark(unread=False), - self.setup_bookmark(unread=False), - self.setup_bookmark(unread=False), + self.setup_bookmark(unread=True) + self.setup_bookmark(unread=True) + self.setup_bookmark(unread=False) + self.setup_bookmark(unread=False) + self.setup_bookmark(unread=False) + self.setup_bookmark(unread=False) # without unread parameter response = self.client.get(reverse("linkding:feeds.all", args=[self.token.key])) @@ -356,9 +355,9 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin): response = self.client.get(reverse("linkding:feeds.all", args=[self.token.key])) self.assertEqual(response.status_code, 200) self.assertContains(response, "", count=1) - self.assertContains(response, f"test\n\r\ttitle", count=1) + self.assertContains(response, "test\n\r\ttitle", count=1) self.assertContains( - response, f"test\n\r\tdescription", count=1 + response, "test\n\r\tdescription", count=1 ) def test_sanitize_with_none_text(self): @@ -371,8 +370,8 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark(tags=[tag1]), ] - self.setup_bookmark(), - self.setup_bookmark(), + self.setup_bookmark() + self.setup_bookmark() bundle = self.setup_bundle(all_tags=tag1.name) diff --git a/bookmarks/tests/test_feeds_performance.py b/bookmarks/tests/test_feeds_performance.py index 3cc03eb..d642f08 100644 --- a/bookmarks/tests/test_feeds_performance.py +++ b/bookmarks/tests/test_feeds_performance.py @@ -9,7 +9,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class FeedsPerformanceTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_health_view.py b/bookmarks/tests/test_health_view.py index ccc0c47..2f39df7 100644 --- a/bookmarks/tests/test_health_view.py +++ b/bookmarks/tests/test_health_view.py @@ -7,7 +7,6 @@ from bookmarks.views.settings import app_version class HealthViewTestCase(TestCase): - def test_health_healthy(self): response = self.client.get("/health") diff --git a/bookmarks/tests/test_importer.py b/bookmarks/tests/test_importer.py index 174ab71..acd170a 100644 --- a/bookmarks/tests/test_importer.py +++ b/bookmarks/tests/test_importer.py @@ -1,4 +1,3 @@ -from typing import List from unittest.mock import patch from django.test import TestCase, override_settings @@ -6,19 +5,18 @@ from django.utils import timezone from bookmarks.models import Bookmark, Tag, parse_tag_string from bookmarks.services import tasks -from bookmarks.services.importer import import_netscape_html, ImportOptions +from bookmarks.services.importer import ImportOptions, import_netscape_html from bookmarks.tests.helpers import ( BookmarkFactoryMixin, - ImportTestMixin, BookmarkHtmlTag, + ImportTestMixin, disable_logging, ) from bookmarks.utils import parse_timestamp class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): - - def assertBookmarksImported(self, html_tags: List[BookmarkHtmlTag]): + def assertBookmarksImported(self, html_tags: list[BookmarkHtmlTag]): for html_tag in html_tags: bookmark = Bookmark.objects.get(url=html_tag.href) self.assertIsNotNone(bookmark) @@ -299,7 +297,7 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): @override_settings(USE_TZ=False) def test_use_current_date_when_no_add_date(self): test_html = self.render_html( - tags_html=f""" + tags_html="""
Example.com
Example.com """ @@ -315,7 +313,7 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): def test_use_add_date_when_no_last_modified(self): test_html = self.render_html( - tags_html=f""" + tags_html="""
Example.com
Example.com """ @@ -354,7 +352,7 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): def test_replace_whitespace_in_tag_names(self): test_html = self.render_html( - tags_html=f""" + tags_html="""
Example.com
Example.com """ @@ -395,7 +393,7 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): @disable_logging def test_validate_empty_or_missing_bookmark_url(self): test_html = self.render_html( - tags_html=f""" + tags_html="""
Empty URL
Empty URL
Missing URL diff --git a/bookmarks/tests/test_layout.py b/bookmarks/tests/test_layout.py index 2f3c6e2..801457d 100644 --- a/bookmarks/tests/test_layout.py +++ b/bookmarks/tests/test_layout.py @@ -6,7 +6,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class LayoutTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -19,14 +18,14 @@ class LayoutTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): self.assertInHTML( f""" - Shared + Shared """, html, count=0, ) self.assertInHTML( f""" - Shared bookmarks + Shared bookmarks """, html, count=0, @@ -39,14 +38,14 @@ class LayoutTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): self.assertInHTML( f""" - Shared + Shared """, html, count=1, ) self.assertInHTML( f""" - Shared bookmarks + Shared bookmarks """, html, count=1, diff --git a/bookmarks/tests/test_linkding_middleware.py b/bookmarks/tests/test_linkding_middleware.py index 9280b4e..501394d 100644 --- a/bookmarks/tests/test_linkding_middleware.py +++ b/bookmarks/tests/test_linkding_middleware.py @@ -1,9 +1,9 @@ from django.test import TestCase from django.urls import reverse -from bookmarks.models import UserProfile, GlobalSettings -from bookmarks.tests.helpers import BookmarkFactoryMixin from bookmarks.middlewares import standard_profile +from bookmarks.models import GlobalSettings, UserProfile +from bookmarks.tests.helpers import BookmarkFactoryMixin class LinkdingMiddlewareTestCase(TestCase, BookmarkFactoryMixin): diff --git a/bookmarks/tests/test_login_view.py b/bookmarks/tests/test_login_view.py index 52e1ad7..566c4c5 100644 --- a/bookmarks/tests/test_login_view.py +++ b/bookmarks/tests/test_login_view.py @@ -1,5 +1,5 @@ from django.test import TestCase, override_settings -from django.urls import path, include +from django.urls import include, path from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin from bookmarks.urls import urlpatterns as base_patterns @@ -10,7 +10,6 @@ urlpatterns = base_patterns + [path("oidc/", include("mozilla_django_oidc.urls") @override_settings(ROOT_URLCONF=__name__) class LoginViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def test_failed_login_should_return_401(self): response = self.client.post("/login/", {"username": "test", "password": "test"}) self.assertEqual(response.status_code, 401) diff --git a/bookmarks/tests/test_metadata_view.py b/bookmarks/tests/test_metadata_view.py index 2129ab0..70153ed 100644 --- a/bookmarks/tests/test_metadata_view.py +++ b/bookmarks/tests/test_metadata_view.py @@ -2,7 +2,6 @@ from django.test import TestCase, override_settings class MetadataViewTestCase(TestCase): - def test_default_manifest(self): response = self.client.get("/manifest.json") diff --git a/bookmarks/tests/test_monolith_service.py b/bookmarks/tests/test_monolith_service.py index e9ed1cc..8682e48 100644 --- a/bookmarks/tests/test_monolith_service.py +++ b/bookmarks/tests/test_monolith_service.py @@ -1,7 +1,7 @@ import gzip import os -from unittest import mock import subprocess +from unittest import mock from django.test import TestCase diff --git a/bookmarks/tests/test_opensearch_view.py b/bookmarks/tests/test_opensearch_view.py index 3693d06..b9982c3 100644 --- a/bookmarks/tests/test_opensearch_view.py +++ b/bookmarks/tests/test_opensearch_view.py @@ -3,7 +3,6 @@ from django.urls import reverse class OpenSearchViewTestCase(TestCase): - def test_opensearch_configuration(self): response = self.client.get(reverse("linkding:opensearch")) self.assertEqual(response.status_code, 200) diff --git a/bookmarks/tests/test_pagination_tag.py b/bookmarks/tests/test_pagination_tag.py index 1200240..fad3fd6 100644 --- a/bookmarks/tests/test_pagination_tag.py +++ b/bookmarks/tests/test_pagination_tag.py @@ -1,12 +1,11 @@ from django.core.paginator import Paginator -from django.template import Template, RequestContext -from django.test import TestCase, RequestFactory +from django.template import RequestContext, Template +from django.test import RequestFactory, TestCase from bookmarks.tests.helpers import BookmarkFactoryMixin class PaginationTagTest(TestCase, BookmarkFactoryMixin): - def render_template( self, num_items: int, page_size: int, current_page: int, url: str = "/test" ) -> str: @@ -18,7 +17,7 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin): page = paginator.page(current_page) context = RequestContext(request, {"page": page}) - template_to_render = Template("{% load pagination %}" "{% pagination page %}") + template_to_render = Template("{% load pagination %}{% pagination page %}") return template_to_render.render(context) def assertPrevLinkDisabled(self, html: str): @@ -32,15 +31,13 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin): ) def assertPrevLink(self, html: str, page_number: int, href: str = None): - href = href if href else "/test?page={0}".format(page_number) + href = href if href else f"/test?page={page_number}" self.assertInHTML( - """ + f"""
  • - Previous + Previous
  • - """.format( - href - ), + """, html, ) @@ -55,15 +52,13 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin): ) def assertNextLink(self, html: str, page_number: int, href: str = None): - href = href if href else "/test?page={0}".format(page_number) + href = href if href else f"/test?page={page_number}" self.assertInHTML( - """ + f"""
  • - Next + Next
  • - """.format( - href - ), + """, html, ) @@ -76,15 +71,13 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin): href: str = None, ): active_class = "active" if active else "" - href = href if href else "/test?page={0}".format(page_number) + href = href if href else f"/test?page={page_number}" self.assertInHTML( - """ -
  • - {0} + f""" +
  • + {page_number}
  • - """.format( - page_number, active_class, href - ), + """, html, count=count, ) diff --git a/bookmarks/tests/test_parser.py b/bookmarks/tests/test_parser.py index 4189c65..d575b69 100644 --- a/bookmarks/tests/test_parser.py +++ b/bookmarks/tests/test_parser.py @@ -1,16 +1,13 @@ -from typing import List - from django.test import TestCase from bookmarks.models import parse_tag_string -from bookmarks.services.parser import NetscapeBookmark -from bookmarks.services.parser import parse -from bookmarks.tests.helpers import ImportTestMixin, BookmarkHtmlTag +from bookmarks.services.parser import NetscapeBookmark, parse +from bookmarks.tests.helpers import BookmarkHtmlTag, ImportTestMixin class ParserTestCase(TestCase, ImportTestMixin): def assertTagsEqual( - self, bookmarks: List[NetscapeBookmark], html_tags: List[BookmarkHtmlTag] + self, bookmarks: list[NetscapeBookmark], html_tags: list[BookmarkHtmlTag] ): self.assertEqual(len(bookmarks), len(html_tags)) for bookmark in bookmarks: diff --git a/bookmarks/tests/test_preview_image_loader.py b/bookmarks/tests/test_preview_image_loader.py index df61ce5..4e1fa14 100644 --- a/bookmarks/tests/test_preview_image_loader.py +++ b/bookmarks/tests/test_preview_image_loader.py @@ -63,9 +63,11 @@ class PreviewImageLoaderTestCase(TestCase): url="https://example.com/image.png", icon_data=mock_image_data, content_type="image/png", - content_length=len(mock_image_data), + content_length=None, status_code=200, ): + if not content_length: + content_length = len(icon_data) mock_response = mock.Mock() mock_response.raw = io.BytesIO(icon_data) return MockStreamingResponse( diff --git a/bookmarks/tests/test_queries.py b/bookmarks/tests/test_queries.py index df57d19..e35da3e 100644 --- a/bookmarks/tests/test_queries.py +++ b/bookmarks/tests/test_queries.py @@ -977,10 +977,10 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): ] # Unshared bookmarks - self.setup_bookmark(user=user1, shared=False, title="test title"), - self.setup_bookmark(user=user2, shared=False), - self.setup_bookmark(user=user3, shared=False, tags=[tag]), - self.setup_bookmark(user=user4, shared=True, tags=[tag]), + self.setup_bookmark(user=user1, shared=False, title="test title") + self.setup_bookmark(user=user2, shared=False) + self.setup_bookmark(user=user3, shared=False, tags=[tag]) + self.setup_bookmark(user=user4, shared=True, tags=[tag]) # Should return shared bookmarks from all users query_set = queries.query_shared_bookmarks( @@ -1023,20 +1023,14 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): self.setup_tag(user=user3), ] - self.setup_bookmark(user=user1, shared=True, tags=[shared_tags[0]]), - self.setup_bookmark(user=user2, shared=True, tags=[shared_tags[1]]), - self.setup_bookmark(user=user3, shared=True, tags=[shared_tags[2]]), + self.setup_bookmark(user=user1, shared=True, tags=[shared_tags[0]]) + self.setup_bookmark(user=user2, shared=True, tags=[shared_tags[1]]) + self.setup_bookmark(user=user3, shared=True, tags=[shared_tags[2]]) - self.setup_bookmark( - user=user1, shared=False, tags=[self.setup_tag(user=user1)] - ), - self.setup_bookmark( - user=user2, shared=False, tags=[self.setup_tag(user=user2)] - ), - self.setup_bookmark( - user=user3, shared=False, tags=[self.setup_tag(user=user3)] - ), - self.setup_bookmark(user=user4, shared=True, tags=[self.setup_tag(user=user4)]), + self.setup_bookmark(user=user1, shared=False, tags=[self.setup_tag(user=user1)]) + self.setup_bookmark(user=user2, shared=False, tags=[self.setup_tag(user=user2)]) + self.setup_bookmark(user=user3, shared=False, tags=[self.setup_tag(user=user3)]) + self.setup_bookmark(user=user4, shared=True, tags=[self.setup_tag(user=user4)]) query_set = queries.query_shared_bookmark_tags( None, self.profile, BookmarkSearch(q=""), False @@ -1051,8 +1045,8 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): tag1 = self.setup_tag(user=user1) tag2 = self.setup_tag(user=user2) - self.setup_bookmark(user=user1, shared=True, tags=[tag1]), - self.setup_bookmark(user=user2, shared=True, tags=[tag2]), + self.setup_bookmark(user=user1, shared=True, tags=[tag1]) + self.setup_bookmark(user=user2, shared=True, tags=[tag2]) query_set = queries.query_shared_bookmark_tags( None, self.profile, BookmarkSearch(q=""), True @@ -1074,15 +1068,15 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): # Shared bookmarks self.setup_bookmark( user=users_with_shared_bookmarks[0], shared=True, title="test title" - ), - self.setup_bookmark(user=users_with_shared_bookmarks[1], shared=True), + ) + self.setup_bookmark(user=users_with_shared_bookmarks[1], shared=True) # Unshared bookmarks self.setup_bookmark( user=users_without_shared_bookmarks[0], shared=False, title="test title" - ), - self.setup_bookmark(user=users_without_shared_bookmarks[1], shared=False), - self.setup_bookmark(user=users_without_shared_bookmarks[2], shared=True), + ) + self.setup_bookmark(user=users_without_shared_bookmarks[1], shared=False) + self.setup_bookmark(user=users_without_shared_bookmarks[2], shared=True) # Should return users with shared bookmarks query_set = queries.query_shared_bookmark_users( @@ -1113,25 +1107,25 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): bookmarks = [ self.setup_bookmark( - added=timezone.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2020, 1, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2021, 2, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2021, 2, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2022, 3, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2022, 3, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2023, 4, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2023, 4, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2022, 5, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2022, 5, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2021, 6, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2021, 6, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2020, 7, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2020, 7, 1, tzinfo=datetime.UTC) ), ] sorted_bookmarks = sorted(bookmarks, key=lambda b: b.date_added) @@ -1144,25 +1138,25 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): bookmarks = [ self.setup_bookmark( - added=timezone.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2020, 1, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2021, 2, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2021, 2, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2022, 3, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2022, 3, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2023, 4, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2023, 4, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2022, 5, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2022, 5, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2021, 6, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2021, 6, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( - added=timezone.datetime(2020, 7, 1, tzinfo=datetime.timezone.utc) + added=timezone.datetime(2020, 7, 1, tzinfo=datetime.UTC) ), ] sorted_bookmarks = sorted(bookmarks, key=lambda b: b.date_added, reverse=True) @@ -1227,11 +1221,11 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): # Modify date field on bookmark directly to test modified_since older_bookmark.date_modified = timezone.datetime( - 2025, 1, 1, tzinfo=datetime.timezone.utc + 2025, 1, 1, tzinfo=datetime.UTC ) older_bookmark.save() recent_bookmark.date_modified = timezone.datetime( - 2025, 5, 15, tzinfo=datetime.timezone.utc + 2025, 5, 15, tzinfo=datetime.UTC ) recent_bookmark.save() @@ -1264,11 +1258,11 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): # Create bookmarks with different dates older_bookmark = self.setup_bookmark( title="old bookmark", - added=timezone.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc), + added=timezone.datetime(2025, 1, 1, tzinfo=datetime.UTC), ) recent_bookmark = self.setup_bookmark( title="recent bookmark", - added=timezone.datetime(2025, 5, 15, tzinfo=datetime.timezone.utc), + added=timezone.datetime(2025, 5, 15, tzinfo=datetime.UTC), ) # Test with date between the two bookmarks @@ -1523,9 +1517,9 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): # Bookmarks that should not match self.setup_bookmark(is_archived=True, tags=[other_tag]) self.setup_bookmark(is_archived=True) - self.setup_bookmark(tags=[tag1]), - self.setup_bookmark(tags=[tag2]), - self.setup_bookmark(tags=[tag1, tag2]), + self.setup_bookmark(tags=[tag1]) + self.setup_bookmark(tags=[tag2]) + self.setup_bookmark(tags=[tag1, tag2]) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) @@ -1551,9 +1545,9 @@ class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): # Bookmarks that should not match self.setup_bookmark(user=user1, shared=True, tags=[other_tag]) self.setup_bookmark(user=user2, shared=True) - self.setup_bookmark(user=user1, shared=False, tags=[tag1]), - self.setup_bookmark(user=user2, shared=False, tags=[tag2]), - self.setup_bookmark(user=user1, shared=False, tags=[tag1, tag2]), + self.setup_bookmark(user=user1, shared=False, tags=[tag1]) + self.setup_bookmark(user=user2, shared=False, tags=[tag2]) + self.setup_bookmark(user=user1, shared=False, tags=[tag1, tag2]) query = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q="", bundle=bundle), False @@ -1571,7 +1565,6 @@ class QueriesLegacySearchTestCase(QueriesBasicTestCase): class QueriesAdvancedSearchTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self): self.user = self.get_or_create_test_user() self.profile = self.user.profile diff --git a/bookmarks/tests/test_search_query_parser.py b/bookmarks/tests/test_search_query_parser.py index 9041899..4f28f82 100644 --- a/bookmarks/tests/test_search_query_parser.py +++ b/bookmarks/tests/test_search_query_parser.py @@ -1,22 +1,22 @@ from django.test import TestCase -from bookmarks.services.search_query_parser import ( - SearchQueryTokenizer, - TokenType, - SearchExpression, - TermExpression, - TagExpression, - SpecialKeywordExpression, - AndExpression, - OrExpression, - NotExpression, - SearchQueryParseError, - parse_search_query, - expression_to_string, - strip_tag_from_query, - extract_tag_names_from_query, -) from bookmarks.models import UserProfile +from bookmarks.services.search_query_parser import ( + AndExpression, + NotExpression, + OrExpression, + SearchExpression, + SearchQueryParseError, + SearchQueryTokenizer, + SpecialKeywordExpression, + TagExpression, + TermExpression, + TokenType, + expression_to_string, + extract_tag_names_from_query, + parse_search_query, + strip_tag_from_query, +) def _term(term: str) -> TermExpression: diff --git a/bookmarks/tests/test_settings_export_view.py b/bookmarks/tests/test_settings_export_view.py index 9593bd9..7321b80 100644 --- a/bookmarks/tests/test_settings_export_view.py +++ b/bookmarks/tests/test_settings_export_view.py @@ -9,7 +9,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -81,9 +80,7 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark() # Mock timezone.now to return a fixed datetime for predictable filename - fixed_time = datetime.datetime( - 2023, 5, 15, 14, 30, 45, tzinfo=datetime.timezone.utc - ) + fixed_time = datetime.datetime(2023, 5, 15, 14, 30, 45, tzinfo=datetime.UTC) with patch("bookmarks.views.settings.timezone.now", return_value=fixed_time): response = self.client.get(reverse("linkding:settings.export"), follow=True) diff --git a/bookmarks/tests/test_settings_general_view.py b/bookmarks/tests/test_settings_general_view.py index bb8158d..5c63d97 100644 --- a/bookmarks/tests/test_settings_general_view.py +++ b/bookmarks/tests/test_settings_general_view.py @@ -1,20 +1,19 @@ import hashlib import random -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch import requests from django.test import TestCase, override_settings from django.urls import reverse from requests import RequestException -from bookmarks.models import UserProfile, GlobalSettings +from bookmarks.models import GlobalSettings, UserProfile from bookmarks.services import tasks from bookmarks.tests.helpers import BookmarkFactoryMixin from bookmarks.views.settings import app_version, get_version_info class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -57,7 +56,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin): def assertSuccessMessage(self, html, message: str, count=1): self.assertInHTML( f""" -
    { message }
    +
    {message}
    """, html, count=count, @@ -66,7 +65,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin): def assertErrorMessage(self, html, message: str, count=1): self.assertInHTML( f""" -
    { message }
    +
    {message}
    """, html, count=count, @@ -456,7 +455,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin): def test_get_version_info_shows_latest_version_when_versions_are_not_equal(self): latest_version_response_mock = Mock( - status_code=200, json=lambda: {"name": f"v123.0.1"} + status_code=200, json=lambda: {"name": "v123.0.1"} ) with patch.object(requests, "get", return_value=latest_version_response_mock): version_info = get_version_info(random.random()) diff --git a/bookmarks/tests/test_settings_import_view.py b/bookmarks/tests/test_settings_import_view.py index 0443da3..7789529 100644 --- a/bookmarks/tests/test_settings_import_view.py +++ b/bookmarks/tests/test_settings_import_view.py @@ -6,7 +6,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging class SettingsImportViewTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -14,7 +13,7 @@ class SettingsImportViewTestCase(TestCase, BookmarkFactoryMixin): def assertSuccessMessage(self, response, message: str): self.assertInHTML( f""" -
    { message }
    +
    {message}
    """, response.content.decode("utf-8"), ) @@ -25,7 +24,7 @@ class SettingsImportViewTestCase(TestCase, BookmarkFactoryMixin): def assertErrorMessage(self, response, message: str): self.assertInHTML( f""" -
    { message }
    +
    {message}
    """, response.content.decode("utf-8"), ) diff --git a/bookmarks/tests/test_settings_integrations_view.py b/bookmarks/tests/test_settings_integrations_view.py index 93fe4df..11fe90e 100644 --- a/bookmarks/tests/test_settings_integrations_view.py +++ b/bookmarks/tests/test_settings_integrations_view.py @@ -1,12 +1,11 @@ from django.test import TestCase from django.urls import reverse -from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin from bookmarks.models import ApiToken, FeedToken +from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class SettingsIntegrationsViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) diff --git a/bookmarks/tests/test_singlefile_service.py b/bookmarks/tests/test_singlefile_service.py index 20e6c3b..98c8611 100644 --- a/bookmarks/tests/test_singlefile_service.py +++ b/bookmarks/tests/test_singlefile_service.py @@ -28,9 +28,11 @@ class SingleFileServiceTestCase(TestCase): singlefile.create_snapshot("http://example.com", "nonexistentfile.tmp") # so also check that it raises error if output file isn't created - with mock.patch("subprocess.Popen"): - with self.assertRaises(singlefile.SingleFileError): - singlefile.create_snapshot("http://example.com", "nonexistentfile.tmp") + with ( + mock.patch("subprocess.Popen"), + self.assertRaises(singlefile.SingleFileError), + ): + singlefile.create_snapshot("http://example.com", "nonexistentfile.tmp") def test_create_snapshot_empty_options(self): mock_process = mock.Mock() diff --git a/bookmarks/tests/test_tag_cloud_template.py b/bookmarks/tests/test_tag_cloud_template.py index ad734d6..86c3220 100644 --- a/bookmarks/tests/test_tag_cloud_template.py +++ b/bookmarks/tests/test_tag_cloud_template.py @@ -1,9 +1,7 @@ -from typing import List, Type - -from django.contrib.auth.models import User, AnonymousUser +from django.contrib.auth.models import AnonymousUser, User from django.http import HttpResponse -from django.template import Template, RequestContext -from django.test import TestCase, RequestFactory +from django.template import RequestContext, Template +from django.test import RequestFactory, TestCase from bookmarks.middlewares import LinkdingMiddleware from bookmarks.models import BookmarkSearch, UserProfile @@ -14,7 +12,7 @@ from bookmarks.views import contexts class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def render_template( self, - context_type: Type[contexts.TagCloudContext] = contexts.ActiveTagCloudContext, + context_type: type[contexts.TagCloudContext] = contexts.ActiveTagCloudContext, url: str = "/test", user: User | AnonymousUser = None, ): @@ -35,7 +33,7 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def assertTagGroups( self, rendered_template: str, - groups: List[List[str]], + groups: list[list[str]], highlight_first_char: bool = True, ): soup = self.make_soup(rendered_template) diff --git a/bookmarks/tests/test_tags_index_view.py b/bookmarks/tests/test_tags_index_view.py index 19f3572..377e797 100644 --- a/bookmarks/tests/test_tags_index_view.py +++ b/bookmarks/tests/test_tags_index_view.py @@ -132,7 +132,7 @@ class TagsIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def test_pagination(self): tags = [] - for i in range(75): + for _i in range(75): tags.append(self.setup_tag()) response = self.client.get(reverse("linkding:tags.index")) diff --git a/bookmarks/tests/test_tags_merge_view.py b/bookmarks/tests/test_tags_merge_view.py index 387d4bf..624155c 100644 --- a/bookmarks/tests/test_tags_merge_view.py +++ b/bookmarks/tests/test_tags_merge_view.py @@ -1,5 +1,5 @@ from bs4 import TemplateString -from bs4.element import NavigableString, CData +from bs4.element import CData, NavigableString from django.test import TestCase from django.urls import reverse @@ -146,7 +146,7 @@ class TagsMergeViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): self.assertIn("This field is required", self.get_text(merge_tags_group)) def test_validate_nonexistent_target_tag(self): - merge_tag = self.setup_tag(name="merge_tag") + self.setup_tag(name="merge_tag") response = self.client.post( reverse("linkding:tags.merge"), diff --git a/bookmarks/tests/test_tags_model.py b/bookmarks/tests/test_tags_model.py index 9cb37a4..aa3d6ba 100644 --- a/bookmarks/tests/test_tags_model.py +++ b/bookmarks/tests/test_tags_model.py @@ -4,7 +4,6 @@ from bookmarks.models import parse_tag_string class TagTestCase(TestCase): - def test_parse_tag_string_returns_list_of_tag_names(self): self.assertCountEqual( parse_tag_string("book, movie, album"), ["book", "movie", "album"] diff --git a/bookmarks/tests/test_tags_service.py b/bookmarks/tests/test_tags_service.py index 84fe5bb..7e02145 100644 --- a/bookmarks/tests/test_tags_service.py +++ b/bookmarks/tests/test_tags_service.py @@ -9,7 +9,6 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class TagServiceTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self) -> None: self.get_or_create_test_user() diff --git a/bookmarks/tests/test_toasts_view.py b/bookmarks/tests/test_toasts_view.py index 2bc9fd7..f665298 100644 --- a/bookmarks/tests/test_toasts_view.py +++ b/bookmarks/tests/test_toasts_view.py @@ -6,13 +6,12 @@ from bookmarks.models import Toast from bookmarks.tests.helpers import ( BookmarkFactoryMixin, HtmlTestMixin, - random_sentence, disable_logging, + random_sentence, ) class ToastsViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def setUp(self) -> None: user = self.get_or_create_test_user() self.client.force_login(user) @@ -73,7 +72,7 @@ class ToastsViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def test_form_tag(self): self.create_toast() - expected_action = f'{reverse("linkding:toasts.acknowledge")}?return_url={reverse("linkding:bookmarks.index")}' + expected_action = f"{reverse('linkding:toasts.acknowledge')}?return_url={reverse('linkding:bookmarks.index')}" response = self.client.get(reverse("linkding:bookmarks.index")) soup = self.make_soup(response.content.decode()) diff --git a/bookmarks/tests/test_user_profile_model.py b/bookmarks/tests/test_user_profile_model.py index 742ae40..9ff8f21 100644 --- a/bookmarks/tests/test_user_profile_model.py +++ b/bookmarks/tests/test_user_profile_model.py @@ -5,7 +5,6 @@ from bookmarks.models import UserProfile class UserProfileTestCase(TestCase): - def test_create_user_should_init_profile(self): user = User.objects.create_user("testuser", "test@example.com", "password123") profile = UserProfile.objects.all().filter(user_id=user.id).first() diff --git a/bookmarks/tests/test_user_select_tag.py b/bookmarks/tests/test_user_select_tag.py index 39de3de..24dbb8d 100644 --- a/bookmarks/tests/test_user_select_tag.py +++ b/bookmarks/tests/test_user_select_tag.py @@ -1,5 +1,5 @@ -from django.template import Template, RequestContext -from django.test import TestCase, RequestFactory +from django.template import RequestContext, Template +from django.test import RequestFactory, TestCase from bookmarks.models import BookmarkSearch, User from bookmarks.tests.helpers import BookmarkFactoryMixin @@ -27,7 +27,7 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin): def assertUserOption(self, html: str, user: User, selected: bool = False): self.assertInHTML( f""" - """, @@ -53,8 +53,8 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin): rendered_template = self.render_template("/test") self.assertInHTML( - f""" - + """ + """, rendered_template, ) diff --git a/bookmarks/tests/test_utils.py b/bookmarks/tests/test_utils.py index 7bdeb38..ae67b98 100644 --- a/bookmarks/tests/test_utils.py +++ b/bookmarks/tests/test_utils.py @@ -7,13 +7,12 @@ from django.utils import timezone from bookmarks.utils import ( humanize_absolute_date, humanize_relative_date, - parse_timestamp, normalize_url, + parse_timestamp, ) class UtilsTestCase(TestCase): - def test_humanize_absolute_date(self): test_cases = [ ( diff --git a/bookmarks/tests/test_website_loader.py b/bookmarks/tests/test_website_loader.py index ce2ef15..5a6f009 100644 --- a/bookmarks/tests/test_website_loader.py +++ b/bookmarks/tests/test_website_loader.py @@ -1,8 +1,9 @@ from unittest import mock -from bookmarks.services import website_loader from django.test import TestCase +from bookmarks.services import website_loader + class MockStreamingResponse: def __init__(self, num_chunks, chunk_size, insert_head_after_chunk=None): @@ -12,7 +13,7 @@ class MockStreamingResponse: self.chunks.append(chunk.encode("utf-8")) if index == insert_head_after_chunk: - self.chunks.append("".encode("utf-8")) + self.chunks.append(b"") def iter_content(self, **kwargs): return self.chunks @@ -92,7 +93,7 @@ class WebsiteLoaderTestCase(TestCase): def test_load_page_removes_bytes_after_end_of_head(self): with mock.patch("requests.get") as mock_get: mock_response = MockStreamingResponse(num_chunks=1, chunk_size=0) - mock_response.chunks[0] = "人".encode("utf-8") + mock_response.chunks[0] = "人".encode() # add a single byte that can't be decoded to utf-8 mock_response.chunks[0] += 0xFF.to_bytes(1, "big") mock_get.return_value = mock_response diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py b/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py index 13cb8b0..3d87efb 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py @@ -2,8 +2,8 @@ from django.test import override_settings from django.urls import reverse from playwright.sync_api import expect -from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import Bookmark +from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py b/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py index 8569a60..fb5a8dd 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py @@ -1,8 +1,8 @@ from django.urls import reverse from playwright.sync_api import expect -from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import Bookmark +from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py b/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py index 15e0c9e..d566f98 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py @@ -1,5 +1,3 @@ -from typing import List - from django.urls import reverse from playwright.sync_api import expect @@ -23,7 +21,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): user=self.setup_user(enable_sharing=True), ) - def assertVisibleBookmarks(self, titles: List[str]): + def assertVisibleBookmarks(self, titles: list[str]): bookmark_tags = self.page.locator("ul.bookmark-list > li") expect(bookmark_tags).to_have_count(len(titles)) @@ -31,7 +29,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): matching_tag = bookmark_tags.filter(has_text=title) expect(matching_tag).to_be_visible() - def assertVisibleTags(self, titles: List[str]): + def assertVisibleTags(self, titles: list[str]): tag_tags = self.page.locator(".tag-cloud .unselected-tags a") expect(tag_tags).to_have_count(len(titles)) diff --git a/bookmarks/tests_e2e/e2e_test_bundle_preview.py b/bookmarks/tests_e2e/e2e_test_bundle_preview.py index 11d098a..bf8c8ef 100644 --- a/bookmarks/tests_e2e/e2e_test_bundle_preview.py +++ b/bookmarks/tests_e2e/e2e_test_bundle_preview.py @@ -13,7 +13,7 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): page = self.open(reverse("linkding:bundles.new")) expect( - page.get_by_text(f"Found 6 bookmarks matching this bundle") + page.get_by_text("Found 6 bookmarks matching this bundle") ).to_be_visible() self.assertVisibleBookmarks(group1 + group2) @@ -22,7 +22,7 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): search.fill("foo") expect( - page.get_by_text(f"Found 3 bookmarks matching this bundle") + page.get_by_text("Found 3 bookmarks matching this bundle") ).to_be_visible() self.assertVisibleBookmarks(group1) @@ -30,7 +30,7 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): search.fill("bar") expect( - page.get_by_text(f"Found 3 bookmarks matching this bundle") + page.get_by_text("Found 3 bookmarks matching this bundle") ).to_be_visible() self.assertVisibleBookmarks(group2) @@ -38,7 +38,7 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): search.fill("invalid") expect( - page.get_by_text(f"No bookmarks match the current bundle") + page.get_by_text("No bookmarks match the current bundle") ).to_be_visible() self.assertVisibleBookmarks([]) diff --git a/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py b/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py index 806398c..a3a032e 100644 --- a/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py +++ b/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py @@ -5,7 +5,6 @@ from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class CollapseSidePanelE2ETestCase(LinkdingE2ETestCase): - def setUp(self) -> None: super().setUp() diff --git a/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py b/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py index f55ef27..bad86a7 100644 --- a/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py +++ b/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py @@ -3,8 +3,8 @@ from unittest.mock import patch from django.urls import reverse from playwright.sync_api import expect -from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.services import website_loader +from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase mock_website_metadata = website_loader.WebsiteMetadata( url="https://example.com", @@ -15,7 +15,6 @@ mock_website_metadata = website_loader.WebsiteMetadata( class BookmarkFormE2ETestCase(LinkdingE2ETestCase): - def setUp(self) -> None: super().setUp() self.website_loader_patch = patch.object( diff --git a/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py b/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py index 6d3db6c..42412c0 100644 --- a/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py +++ b/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py @@ -17,7 +17,6 @@ mock_website_metadata = website_loader.WebsiteMetadata( class BookmarkFormE2ETestCase(LinkdingE2ETestCase): - def setUp(self) -> None: super().setUp() self.website_loader_patch = patch.object( diff --git a/bookmarks/tests_e2e/e2e_test_settings_general.py b/bookmarks/tests_e2e/e2e_test_settings_general.py index 193aac1..52b570b 100644 --- a/bookmarks/tests_e2e/e2e_test_settings_general.py +++ b/bookmarks/tests_e2e/e2e_test_settings_general.py @@ -1,8 +1,8 @@ from django.urls import reverse from playwright.sync_api import expect -from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import UserProfile +from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class SettingsGeneralE2ETestCase(LinkdingE2ETestCase): diff --git a/bookmarks/tests_e2e/helpers.py b/bookmarks/tests_e2e/helpers.py index ff2e7eb..3355248 100644 --- a/bookmarks/tests_e2e/helpers.py +++ b/bookmarks/tests_e2e/helpers.py @@ -1,8 +1,7 @@ import os from django.contrib.staticfiles.testing import LiveServerTestCase -from playwright.sync_api import BrowserContext, Page, sync_playwright -from playwright.sync_api import expect +from playwright.sync_api import BrowserContext, Page, expect, sync_playwright from bookmarks.tests.helpers import BookmarkFactoryMixin diff --git a/bookmarks/type_defs.py b/bookmarks/type_defs.py index 4c1fe9f..a3ee601 100644 --- a/bookmarks/type_defs.py +++ b/bookmarks/type_defs.py @@ -5,7 +5,7 @@ Stuff in here is only used for type hints from django import http from django.contrib.auth.models import AnonymousUser -from bookmarks.models import GlobalSettings, UserProfile, User +from bookmarks.models import GlobalSettings, User, UserProfile class HttpRequest(http.HttpRequest): diff --git a/bookmarks/urls.py b/bookmarks/urls.py index 3ec6ad4..34a6e66 100644 --- a/bookmarks/urls.py +++ b/bookmarks/urls.py @@ -1,82 +1,93 @@ -from django.contrib.auth import views as auth_views -from django.urls import path, include -from django.urls import re_path from django.conf import settings +from django.contrib.auth import views as django_auth_views +from django.urls import include, path, re_path -from bookmarks import feeds, views +from bookmarks import feeds from bookmarks.admin import linkding_admin_site from bookmarks.api import routes as api_routes +from bookmarks.views import assets as assets_views +from bookmarks.views import auth as linkding_auth_views +from bookmarks.views import bookmarks as bookmarks_views +from bookmarks.views import bundles as bundles_views +from bookmarks.views import settings as settings_views +from bookmarks.views import tags as tags_views +from bookmarks.views import toasts as toasts_views +from bookmarks.views.custom_css import custom_css as custom_css_view +from bookmarks.views.health import health as health_view +from bookmarks.views.manifest import manifest as manifest_view +from bookmarks.views.opensearch import opensearch as opensearch_view +from bookmarks.views.root import root as root_view urlpatterns = [ # Root view handling redirection based on user authentication - re_path(r"^$", views.root, name="root"), + re_path(r"^$", root_view, name="root"), # Bookmarks - path("bookmarks", views.bookmarks.index, name="bookmarks.index"), + path("bookmarks", bookmarks_views.index, name="bookmarks.index"), path( - "bookmarks/action", views.bookmarks.index_action, name="bookmarks.index.action" + "bookmarks/action", bookmarks_views.index_action, name="bookmarks.index.action" ), - path("bookmarks/archived", views.bookmarks.archived, name="bookmarks.archived"), + path("bookmarks/archived", bookmarks_views.archived, name="bookmarks.archived"), path( "bookmarks/archived/action", - views.bookmarks.archived_action, + bookmarks_views.archived_action, name="bookmarks.archived.action", ), - path("bookmarks/shared", views.bookmarks.shared, name="bookmarks.shared"), + path("bookmarks/shared", bookmarks_views.shared, name="bookmarks.shared"), path( "bookmarks/shared/action", - views.bookmarks.shared_action, + bookmarks_views.shared_action, name="bookmarks.shared.action", ), - path("bookmarks/new", views.bookmarks.new, name="bookmarks.new"), - path("bookmarks/close", views.bookmarks.close, name="bookmarks.close"), + path("bookmarks/new", bookmarks_views.new, name="bookmarks.new"), + path("bookmarks/close", bookmarks_views.close, name="bookmarks.close"), path( - "bookmarks//edit", views.bookmarks.edit, name="bookmarks.edit" + "bookmarks//edit", bookmarks_views.edit, name="bookmarks.edit" ), # Assets path( "assets/", - views.assets.view, + assets_views.view, name="assets.view", ), path( "assets//read", - views.assets.read, + assets_views.read, name="assets.read", ), # Bundles - path("bundles", views.bundles.index, name="bundles.index"), - path("bundles/action", views.bundles.action, name="bundles.action"), - path("bundles/new", views.bundles.new, name="bundles.new"), - path("bundles//edit", views.bundles.edit, name="bundles.edit"), - path("bundles/preview", views.bundles.preview, name="bundles.preview"), + path("bundles", bundles_views.index, name="bundles.index"), + path("bundles/action", bundles_views.action, name="bundles.action"), + path("bundles/new", bundles_views.new, name="bundles.new"), + path("bundles//edit", bundles_views.edit, name="bundles.edit"), + path("bundles/preview", bundles_views.preview, name="bundles.preview"), # Tags - path("tags", views.tags.tags_index, name="tags.index"), - path("tags/new", views.tags.tag_new, name="tags.new"), - path("tags//edit", views.tags.tag_edit, name="tags.edit"), - path("tags/merge", views.tags.tag_merge, name="tags.merge"), + path("tags", tags_views.tags_index, name="tags.index"), + path("tags/new", tags_views.tag_new, name="tags.new"), + path("tags//edit", tags_views.tag_edit, name="tags.edit"), + path("tags/merge", tags_views.tag_merge, name="tags.merge"), # Settings - path("settings", views.settings.general, name="settings.index"), - path("settings/general", views.settings.general, name="settings.general"), - path("settings/update", views.settings.update, name="settings.update"), + path("settings", settings_views.general, name="settings.index"), + path("settings/general", settings_views.general, name="settings.general"), + path("settings/update", settings_views.update, name="settings.update"), path( "settings/integrations", - views.settings.integrations, + settings_views.integrations, name="settings.integrations", ), path( "settings/integrations/create-api-token", - views.settings.create_api_token, + settings_views.create_api_token, name="settings.integrations.create_api_token", ), path( "settings/integrations/delete-api-token", - views.settings.delete_api_token, + settings_views.delete_api_token, name="settings.integrations.delete_api_token", ), - path("settings/import", views.settings.bookmark_import, name="settings.import"), - path("settings/export", views.settings.bookmark_export, name="settings.export"), + path("settings/import", settings_views.bookmark_import, name="settings.import"), + path("settings/export", settings_views.bookmark_export, name="settings.export"), # Toasts - path("toasts/acknowledge", views.toasts.acknowledge, name="toasts.acknowledge"), + path("toasts/acknowledge", toasts_views.acknowledge, name="toasts.acknowledge"), # API path("api/", include(api_routes.default_router.urls)), path("api/bookmarks/", include(api_routes.bookmark_router.urls)), @@ -97,13 +108,13 @@ urlpatterns = [ ), path("feeds/shared", feeds.PublicSharedBookmarksFeed(), name="feeds.public_shared"), # Health check - path("health", views.health, name="health"), + path("health", health_view, name="health"), # Manifest - path("manifest.json", views.manifest, name="manifest"), + path("manifest.json", manifest_view, name="manifest"), # Custom CSS - path("custom_css", views.custom_css, name="custom_css"), + path("custom_css", custom_css_view, name="custom_css"), # OpenSearch - path("opensearch.xml", views.opensearch, name="opensearch"), + path("opensearch.xml", opensearch_view, name="opensearch"), ] # Live reload (debug only) @@ -119,18 +130,18 @@ urlpatterns = [path("", include((urlpatterns, "linkding")))] urlpatterns += [ path( "login/", - views.auth.LinkdingLoginView.as_view(redirect_authenticated_user=True), + linkding_auth_views.LinkdingLoginView.as_view(redirect_authenticated_user=True), name="login", ), - path("logout/", auth_views.LogoutView.as_view(), name="logout"), + path("logout/", django_auth_views.LogoutView.as_view(), name="logout"), path( "change-password/", - views.auth.LinkdingPasswordChangeView.as_view(), + linkding_auth_views.LinkdingPasswordChangeView.as_view(), name="change_password", ), path( "password-change-done/", - auth_views.PasswordChangeDoneView.as_view(), + django_auth_views.PasswordChangeDoneView.as_view(), name="password_change_done", ), ] diff --git a/bookmarks/utils.py b/bookmarks/utils.py index da814fa..3b1fb7f 100644 --- a/bookmarks/utils.py +++ b/bookmarks/utils.py @@ -1,18 +1,17 @@ +import datetime import logging import re import unicodedata import urllib.parse -import datetime -from typing import Optional from dateutil.relativedelta import relativedelta +from django.conf import settings from django.http import HttpResponseRedirect from django.template.defaultfilters import pluralize -from django.utils import timezone, formats -from django.conf import settings +from django.utils import formats, timezone try: - with open("version.txt", "r") as f: + with open("version.txt") as f: app_version = f.read().strip("\n") except Exception as exc: logging.exception(exc) @@ -35,7 +34,7 @@ weekday_names = { def humanize_absolute_date( - value: datetime.datetime, now: Optional[datetime.datetime] = None + value: datetime.datetime, now: datetime.datetime | None = None ): if not now: now = timezone.now() @@ -55,7 +54,7 @@ def humanize_absolute_date( def humanize_relative_date( - value: datetime.datetime, now: Optional[datetime.datetime] = None + value: datetime.datetime, now: datetime.datetime | None = None ): if not now: now = timezone.now() @@ -89,7 +88,7 @@ def parse_timestamp(value: str): try: timestamp = int(value) except ValueError: - raise ValueError(f"{value} is not a valid timestamp") + raise ValueError(f"{value} is not a valid timestamp") from None try: return datetime.datetime.fromtimestamp(timestamp, datetime.UTC) diff --git a/bookmarks/views/__init__.py b/bookmarks/views/__init__.py index 7c61067..e69de29 100644 --- a/bookmarks/views/__init__.py +++ b/bookmarks/views/__init__.py @@ -1,12 +0,0 @@ -from .assets import * -from .auth import * -from .bookmarks import * -from . import bundles -from . import tags -from .settings import * -from .toasts import * -from .health import health -from .manifest import manifest -from .custom_css import custom_css -from .root import root -from .opensearch import opensearch diff --git a/bookmarks/views/access.py b/bookmarks/views/access.py index 936e143..3850110 100644 --- a/bookmarks/views/access.py +++ b/bookmarks/views/access.py @@ -8,7 +8,7 @@ def bookmark_read(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=int(bookmark_id)) except Bookmark.DoesNotExist: - raise Http404("Bookmark does not exist") + raise Http404("Bookmark does not exist") from None is_owner = bookmark.owner == request.user is_shared = ( @@ -29,7 +29,7 @@ def bookmark_write(request: HttpRequest, bookmark_id: int | str): try: return Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: - raise Http404("Bookmark does not exist") + raise Http404("Bookmark does not exist") from None def bundle_read(request: HttpRequest, bundle_id: int | str): @@ -40,14 +40,14 @@ def bundle_write(request: HttpRequest, bundle_id: int | str): try: return BookmarkBundle.objects.get(pk=bundle_id, owner=request.user) except (BookmarkBundle.DoesNotExist, ValueError): - raise Http404("Bundle does not exist") + raise Http404("Bundle does not exist") from None def asset_read(request: HttpRequest, asset_id: int | str): try: asset = BookmarkAsset.objects.get(pk=asset_id) except BookmarkAsset.DoesNotExist: - raise Http404("Asset does not exist") + raise Http404("Asset does not exist") from None bookmark_read(request, asset.bookmark_id) return asset @@ -57,18 +57,18 @@ def asset_write(request: HttpRequest, asset_id: int | str): try: return BookmarkAsset.objects.get(pk=asset_id, bookmark__owner=request.user) except BookmarkAsset.DoesNotExist: - raise Http404("Asset does not exist") + raise Http404("Asset does not exist") from None def toast_write(request: HttpRequest, toast_id: int | str): try: return Toast.objects.get(pk=toast_id, owner=request.user) except Toast.DoesNotExist: - raise Http404("Toast does not exist") + raise Http404("Toast does not exist") from None def api_token_write(request: HttpRequest, token_id: int | str): try: return ApiToken.objects.get(id=token_id, user=request.user) except (ApiToken.DoesNotExist, ValueError): - raise Http404("API token does not exist") + raise Http404("API token does not exist") from None diff --git a/bookmarks/views/assets.py b/bookmarks/views/assets.py index 688ec05..4048875 100644 --- a/bookmarks/views/assets.py +++ b/bookmarks/views/assets.py @@ -3,8 +3,8 @@ import os from django.conf import settings from django.http import ( - HttpResponse, Http404, + HttpResponse, ) from django.shortcuts import render diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py index d16191b..4be1660 100644 --- a/bookmarks/views/bookmarks.py +++ b/bookmarks/views/bookmarks.py @@ -4,9 +4,9 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.db.models import QuerySet from django.http import ( - HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden, + HttpResponseRedirect, ) from django.shortcuts import render from django.urls import reverse @@ -17,21 +17,22 @@ from bookmarks.models import ( Bookmark, BookmarkSearch, ) -from bookmarks.services import assets as asset_actions, tasks +from bookmarks.services import assets as asset_actions +from bookmarks.services import tasks from bookmarks.services.bookmarks import ( archive_bookmark, archive_bookmarks, - unarchive_bookmark, - unarchive_bookmarks, + create_html_snapshots, delete_bookmarks, - tag_bookmarks, - untag_bookmarks, mark_bookmarks_as_read, mark_bookmarks_as_unread, - share_bookmarks, - unshare_bookmarks, refresh_bookmarks_metadata, - create_html_snapshots, + share_bookmarks, + tag_bookmarks, + unarchive_bookmark, + unarchive_bookmarks, + unshare_bookmarks, + untag_bookmarks, ) from bookmarks.type_defs import HttpRequest from bookmarks.utils import get_safe_return_url @@ -220,13 +221,12 @@ def convert_tag_string(tag_string: str): @login_required def new(request: HttpRequest): form = BookmarkForm(request) - if request.method == "POST": - if form.is_valid(): - form.save() - if form.is_auto_close: - return HttpResponseRedirect(reverse("linkding:bookmarks.close")) - else: - return HttpResponseRedirect(reverse("linkding:bookmarks.index")) + if request.method == "POST" and form.is_valid(): + form.save() + if form.is_auto_close: + return HttpResponseRedirect(reverse("linkding:bookmarks.close")) + else: + return HttpResponseRedirect(reverse("linkding:bookmarks.index")) status = 422 if request.method == "POST" and not form.is_valid() else 200 context = {"form": form, "return_url": reverse("linkding:bookmarks.index")} @@ -242,10 +242,9 @@ def edit(request: HttpRequest, bookmark_id: int): request.GET.get("return_url"), reverse("linkding:bookmarks.index") ) - if request.method == "POST": - if form.is_valid(): - form.save() - return HttpResponseRedirect(return_url) + if request.method == "POST" and form.is_valid(): + form.save() + return HttpResponseRedirect(return_url) status = 422 if request.method == "POST" and not form.is_valid() else 200 context = {"form": form, "bookmark_id": bookmark_id, "return_url": return_url} @@ -397,29 +396,29 @@ def handle_action(request: HttpRequest, query: QuerySet[Bookmark] = None): # Use only selected bookmarks bookmark_ids = request.POST.getlist("bookmark_id") - if "bulk_archive" == bulk_action: + if bulk_action == "bulk_archive": return archive_bookmarks(bookmark_ids, request.user) - if "bulk_unarchive" == bulk_action: + if bulk_action == "bulk_unarchive": return unarchive_bookmarks(bookmark_ids, request.user) - if "bulk_delete" == bulk_action: + if bulk_action == "bulk_delete": return delete_bookmarks(bookmark_ids, request.user) - if "bulk_tag" == bulk_action: + if bulk_action == "bulk_tag": tag_string = convert_tag_string(request.POST["bulk_tag_string"]) return tag_bookmarks(bookmark_ids, tag_string, request.user) - if "bulk_untag" == bulk_action: + if bulk_action == "bulk_untag": tag_string = convert_tag_string(request.POST["bulk_tag_string"]) return untag_bookmarks(bookmark_ids, tag_string, request.user) - if "bulk_read" == bulk_action: + if bulk_action == "bulk_read": return mark_bookmarks_as_read(bookmark_ids, request.user) - if "bulk_unread" == bulk_action: + if bulk_action == "bulk_unread": return mark_bookmarks_as_unread(bookmark_ids, request.user) - if "bulk_share" == bulk_action: + if bulk_action == "bulk_share": return share_bookmarks(bookmark_ids, request.user) - if "bulk_unshare" == bulk_action: + if bulk_action == "bulk_unshare": return unshare_bookmarks(bookmark_ids, request.user) - if "bulk_refresh" == bulk_action: + if bulk_action == "bulk_refresh": return refresh_bookmarks_metadata(bookmark_ids, request.user) - if "bulk_snapshot" == bulk_action: + if bulk_action == "bulk_snapshot": return create_html_snapshots(bookmark_ids, request.user) diff --git a/bookmarks/views/bundles.py b/bookmarks/views/bundles.py index 151a005..754e6fc 100644 --- a/bookmarks/views/bundles.py +++ b/bookmarks/views/bundles.py @@ -51,18 +51,17 @@ def _handle_edit(request: HttpRequest, template: str, bundle: BookmarkBundle = N form = BookmarkBundleForm(form_data, instance=bundle, initial=initial_data) - if request.method == "POST": - if form.is_valid(): - instance = form.save(commit=False) + if request.method == "POST" and form.is_valid(): + instance = form.save(commit=False) - if bundle is None: - instance.order = None - bundles.create_bundle(instance, request.user) - else: - instance.save() + if bundle is None: + instance.order = None + bundles.create_bundle(instance, request.user) + else: + instance.save() - messages.success(request, "Bundle saved successfully.") - return HttpResponseRedirect(reverse("linkding:bundles.index")) + messages.success(request, "Bundle saved successfully.") + return HttpResponseRedirect(reverse("linkding:bundles.index")) status = 422 if request.method == "POST" and not form.is_valid() else 200 bookmark_list = _get_bookmark_list_preview(request, bundle, initial_data) diff --git a/bookmarks/views/contexts.py b/bookmarks/views/contexts.py index 7330cd3..e22a658 100644 --- a/bookmarks/views/contexts.py +++ b/bookmarks/views/contexts.py @@ -1,6 +1,5 @@ import re import urllib.parse -from typing import Set, List from django.conf import settings from django.core.paginator import Paginator @@ -8,23 +7,22 @@ from django.db import models from django.http import Http404 from django.urls import reverse -from bookmarks import queries -from bookmarks import utils +from bookmarks import queries, utils from bookmarks.models import ( Bookmark, BookmarkAsset, BookmarkBundle, BookmarkSearch, BookmarkSearchForm, + Tag, User, UserProfile, - Tag, ) from bookmarks.services.search_query_parser import ( - parse_search_query, - strip_tag_from_query, OrExpression, SearchQueryParseError, + parse_search_query, + strip_tag_from_query, ) from bookmarks.services.wayback import generate_fallback_webarchive_url from bookmarks.type_defs import HttpRequest @@ -411,7 +409,7 @@ class TagGroup: self.tags.append(AddTagItem(self.context, tag)) @staticmethod - def create_tag_groups(context: RequestContext, mode: str, tags: Set[Tag]): + def create_tag_groups(context: RequestContext, mode: str, tags: set[Tag]): if mode == UserProfile.TAG_GROUPING_ALPHABETICAL: return TagGroup._create_tag_groups_alphabetical(context, tags) elif mode == UserProfile.TAG_GROUPING_DISABLED: @@ -420,7 +418,7 @@ class TagGroup: raise ValueError(f"{mode} is not a valid tag grouping mode") @staticmethod - def _create_tag_groups_alphabetical(context: RequestContext, tags: Set[Tag]): + def _create_tag_groups_alphabetical(context: RequestContext, tags: set[Tag]): # Ensure groups, as well as tags within groups, are ordered alphabetically sorted_tags = sorted(tags, key=lambda x: str.lower(x.name)) group = None @@ -447,7 +445,7 @@ class TagGroup: return groups @staticmethod - def _create_tag_groups_disabled(context: RequestContext, tags: Set[Tag]): + def _create_tag_groups_disabled(context: RequestContext, tags: set[Tag]): if len(tags) == 0: return [] @@ -494,7 +492,7 @@ class TagCloudContext: def get_selected_tags(self): raise NotImplementedError("Must be implemented by subclass") - def get_selected_tags_legacy(self, tags: List[Tag]): + def get_selected_tags_legacy(self, tags: list[Tag]): parsed_query = queries.parse_query_string(self.search.q) tag_names = parsed_query["tag_names"] if self.request.user_profile.tag_search == UserProfile.TAG_SEARCH_LAX: diff --git a/bookmarks/views/manifest.py b/bookmarks/views/manifest.py index d52400d..9e6a049 100644 --- a/bookmarks/views/manifest.py +++ b/bookmarks/views/manifest.py @@ -1,5 +1,5 @@ -from django.http import JsonResponse from django.conf import settings +from django.http import JsonResponse def manifest(request): diff --git a/bookmarks/views/opensearch.py b/bookmarks/views/opensearch.py index d8b55ae..b9a648c 100644 --- a/bookmarks/views/opensearch.py +++ b/bookmarks/views/opensearch.py @@ -1,5 +1,5 @@ -from django.urls import reverse from django.shortcuts import render +from django.urls import reverse def opensearch(request): diff --git a/bookmarks/views/settings.py b/bookmarks/views/settings.py index 0eda8c5..89b6e0b 100644 --- a/bookmarks/views/settings.py +++ b/bookmarks/views/settings.py @@ -8,7 +8,7 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.db.models import prefetch_related_objects -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render from django.urls import reverse from django.utils import timezone @@ -16,13 +16,12 @@ from django.utils import timezone from bookmarks.models import ( ApiToken, Bookmark, - UserProfileForm, FeedToken, GlobalSettings, GlobalSettingsForm, + UserProfileForm, ) -from bookmarks.services import exporter, tasks -from bookmarks.services import importer +from bookmarks.services import exporter, importer, tasks from bookmarks.type_defs import HttpRequest from bookmarks.utils import app_version from bookmarks.views import access @@ -272,7 +271,7 @@ def bookmark_import(request: HttpRequest): + " bookmarks could not be imported. Please check the logs for more details." ) messages.error(request, err_msg, "settings_error_message") - except: + except Exception: logging.exception("Unexpected error during bookmark import") messages.error( request, @@ -301,7 +300,7 @@ def bookmark_export(request: HttpRequest): response.write(file_content) return response - except: + except Exception: return render( request, "settings/general.html", diff --git a/bookmarks/views/tags.py b/bookmarks/views/tags.py index ace0598..4e9deff 100644 --- a/bookmarks/views/tags.py +++ b/bookmarks/views/tags.py @@ -4,7 +4,7 @@ from django.core.paginator import Paginator from django.db import transaction from django.db.models import Count from django.http import HttpResponseRedirect -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import get_object_or_404, render from django.urls import reverse from bookmarks.forms import TagForm, TagMergeForm diff --git a/pyproject.toml b/pyproject.toml index 477b356..0112436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ dependencies = [ [dependency-groups] dev = [ - "black>=25.12.0", "coverage>=7.13.1", "django-debug-toolbar>=6.1.0", "djlint>=1.36.4", @@ -33,6 +32,7 @@ dev = [ "pytest>=9.0.2", "pytest-django>=4.11.1", "pytest-xdist>=3.8.0", + "ruff>=0.14.10", ] # For PostgreSQL support, use the binary release for development so that not # everyone needs to build from source. For production, use a separate dependency @@ -57,3 +57,21 @@ profile="django" [tool.djlint.js] indent_size=2 + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +# ignore line length violations +ignore = ["E501"] diff --git a/scripts/generate-changelog.py b/scripts/generate-changelog.py index 3977f8f..da434d6 100755 --- a/scripts/generate-changelog.py +++ b/scripts/generate-changelog.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 -import requests from datetime import datetime +import requests + def load_releases_page(page): url = f'https://api.github.com/repos/sissbruecker/linkding/releases?page={page}' diff --git a/uv.lock b/uv.lock index f69d732..f9718e1 100644 --- a/uv.lock +++ b/uv.lock @@ -24,33 +24,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] -[[package]] -name = "black" -version = "25.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" }, - { url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" }, - { url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" }, - { url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" }, - { url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" }, - { url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" }, - { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, -] - [[package]] name = "bleach" version = "6.3.0" @@ -535,7 +508,6 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "black" }, { name = "coverage" }, { name = "django-debug-toolbar" }, { name = "djlint" }, @@ -544,6 +516,7 @@ dev = [ { name = "pytest" }, { name = "pytest-django" }, { name = "pytest-xdist" }, + { name = "ruff" }, ] postgres = [ { name = "psycopg", extra = ["c"] }, @@ -570,7 +543,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "black", specifier = ">=25.12.0" }, { name = "coverage", specifier = ">=7.13.1" }, { name = "django-debug-toolbar", specifier = ">=6.1.0" }, { name = "djlint", specifier = ">=1.36.4" }, @@ -579,6 +551,7 @@ dev = [ { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-django", specifier = ">=4.11.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "ruff", specifier = ">=0.14.10" }, ] postgres = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.9" }] @@ -606,15 +579,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a9/c1664acf30ef0031ed06650039de1693bddf4cf7f58e07939e1aa80bffb7/mozilla_django_oidc-5.0.2-py3-none-any.whl", hash = "sha256:965a3533b0e299288cdf38ec2f8b550217c302ffe78ce5bd0b2d2f4bc436878b", size = 25910, upload-time = "2025-12-19T16:11:39.729Z" }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "packaging" version = "25.0" @@ -633,15 +597,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] -[[package]] -name = "platformdirs" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, -] - [[package]] name = "playwright" version = "1.57.0" @@ -817,15 +772,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "pytokens" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -941,6 +887,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +] + [[package]] name = "six" version = "1.17.0"