formatting and pylint fixes

This commit is contained in:
Hugh Rundle 2023-10-22 16:52:29 +11:00
parent b34a491172
commit fd1ebf5f71
No known key found for this signature in database
GPG key ID: A7E35779918253F9
11 changed files with 91 additions and 63 deletions

View file

@ -1,14 +1,15 @@
"""Import data from Bookwyrm export files""" """Import data from Bookwyrm export files"""
from bookwyrm import settings
from bookwyrm.models.bookwyrm_import_job import BookwyrmImportJob from bookwyrm.models.bookwyrm_import_job import BookwyrmImportJob
class BookwyrmImporter: class BookwyrmImporter:
"""Import a Bookwyrm User export JSON file. """Import a Bookwyrm User export file.
This is kind of a combination of an importer and a connector. This is kind of a combination of an importer and a connector.
""" """
def process_import(self, user, archive_file, settings): def process_import(
self, user, archive_file, settings
): # pylint: disable=no-self-use
"""import user data from a Bookwyrm export file""" """import user data from a Bookwyrm export file"""
required = [k for k in settings if settings.get(k) == "on"] required = [k for k in settings if settings.get(k) == "on"]

View file

@ -1,4 +1,7 @@
"""Export user account to tar.gz file for import into another Bookwyrm instance"""
import logging import logging
from uuid import uuid4
from django.db.models import FileField from django.db.models import FileField
from django.db.models import Q from django.db.models import Q
@ -6,10 +9,9 @@ from django.core.serializers.json import DjangoJSONEncoder
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from bookwyrm import models from bookwyrm import models
from bookwyrm.models.job import ParentJob, ParentTask
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.tasks import app, IMPORTS from bookwyrm.tasks import app, IMPORTS
from bookwyrm.models.job import ParentJob, ParentTask, SubTask, create_child_job
from uuid import uuid4
from bookwyrm.utils.tar import BookwyrmTarFile from bookwyrm.utils.tar import BookwyrmTarFile
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -49,21 +51,22 @@ def start_export_task(**kwargs):
job.save(update_fields=["export_data"]) job.save(update_fields=["export_data"])
def tar_export(json_data: str, user, f): def tar_export(json_data: str, user, file):
f.open("wb") """wrap the export information in a tar file"""
with BookwyrmTarFile.open(mode="w:gz", fileobj=f) as tar: file.open("wb")
with BookwyrmTarFile.open(mode="w:gz", fileobj=file) as tar:
tar.write_bytes(json_data.encode("utf-8")) tar.write_bytes(json_data.encode("utf-8"))
# Add avatar image if present # Add avatar image if present
if getattr(user, "avatar", False): if getattr(user, "avatar", False):
tar.add_image(user.avatar, filename="avatar") tar.add_image(user.avatar, filename="avatar")
editions, books = get_books_for_user(user) editions = get_books_for_user(user)
for book in editions: for book in editions:
if getattr(book, "cover", False): if getattr(book, "cover", False):
tar.add_image(book.cover) tar.add_image(book.cover)
f.close() file.close()
def json_export(user): def json_export(user):
@ -91,18 +94,19 @@ def json_export(user):
# reading goals # reading goals
reading_goals = models.AnnualGoal.objects.filter(user=user).distinct() reading_goals = models.AnnualGoal.objects.filter(user=user).distinct()
goals_list = [] goals_list = []
# TODO: either error checking should be more sophisticated or maybe we don't need this try/except
try: try:
for goal in reading_goals: for goal in reading_goals:
goals_list.append( goals_list.append(
{"goal": goal.goal, "year": goal.year, "privacy": goal.privacy} {"goal": goal.goal, "year": goal.year, "privacy": goal.privacy}
) )
except Exception: except Exception: # pylint: disable=broad-except
pass pass
try: try:
readthroughs = models.ReadThrough.objects.filter(user=user).distinct().values() readthroughs = models.ReadThrough.objects.filter(user=user).distinct().values()
readthroughs = list(readthroughs) readthroughs = list(readthroughs)
except Exception as e: except Exception: # pylint: disable=broad-except
readthroughs = [] readthroughs = []
# books # books

View file

@ -1,3 +1,5 @@
"""Import a user from another Bookwyrm instance"""
from functools import reduce from functools import reduce
import json import json
import logging import logging
@ -11,13 +13,7 @@ from django.contrib.postgres.fields import ArrayField as DjangoArrayField
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm import models from bookwyrm import models
from bookwyrm.tasks import app, IMPORTS from bookwyrm.tasks import app, IMPORTS
from bookwyrm.models.job import ( from bookwyrm.models.job import ParentJob, ParentTask, SubTask
ParentJob,
ParentTask,
ChildJob,
SubTask,
create_child_job,
)
from bookwyrm.utils.tar import BookwyrmTarFile from bookwyrm.utils.tar import BookwyrmTarFile
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -161,8 +157,10 @@ def get_or_create_edition(book_data, tar):
if cover_path: if cover_path:
tar.write_image_to_file(cover_path, new_book.cover) tar.write_image_to_file(cover_path, new_book.cover)
# NOTE: clean_values removes "last_edited_by" because it's a user ID from the old database # NOTE: clean_values removes "last_edited_by"
# if this is required, bookwyrm_export_job will need to bring in the user who edited it. # because it's a user ID from the old database
# if this is required, bookwyrm_export_job will
# need to bring in the user who edited it.
# create parent # create parent
work = models.Work.objects.create(title=book["title"]) work = models.Work.objects.create(title=book["title"])
@ -197,7 +195,7 @@ def clean_values(data):
return new_data return new_data
def find_existing(cls, data, user): def find_existing(cls, data):
"""Given a book or author, find any existing model instances""" """Given a book or author, find any existing model instances"""
identifiers = [ identifiers = [
@ -248,27 +246,31 @@ def upsert_readthroughs(data, user, book_id):
"""Take a JSON string of readthroughs, find or create the """Take a JSON string of readthroughs, find or create the
instances in the database and return a list of saved instances""" instances in the database and return a list of saved instances"""
for rt in data: for read_thru in data:
start_date = ( start_date = (
parse_datetime(rt["start_date"]) if rt["start_date"] is not None else None parse_datetime(read_thru["start_date"])
if read_thru["start_date"] is not None
else None
) )
finish_date = ( finish_date = (
parse_datetime(rt["finish_date"]) if rt["finish_date"] is not None else None parse_datetime(read_thru["finish_date"])
if read_thru["finish_date"] is not None
else None
) )
stopped_date = ( stopped_date = (
parse_datetime(rt["stopped_date"]) parse_datetime(read_thru["stopped_date"])
if rt["stopped_date"] is not None if read_thru["stopped_date"] is not None
else None else None
) )
readthrough = { readthrough = {
"user": user, "user": user,
"book": models.Edition.objects.get(id=book_id), "book": models.Edition.objects.get(id=book_id),
"progress": rt["progress"], "progress": read_thru["progress"],
"progress_mode": rt["progress_mode"], "progress_mode": read_thru["progress_mode"],
"start_date": start_date, "start_date": start_date,
"finish_date": finish_date, "finish_date": finish_date,
"stopped_date": stopped_date, "stopped_date": stopped_date,
"is_active": rt["is_active"], "is_active": read_thru["is_active"],
} }
existing = models.ReadThrough.objects.filter(**readthrough).exists() existing = models.ReadThrough.objects.filter(**readthrough).exists()
@ -311,7 +313,8 @@ def get_or_create_statuses(user, cls, data, book_id):
def upsert_lists(user, lists, items, book_id): def upsert_lists(user, lists, items, book_id):
"""Take a list and ListItems as JSON and create DB entries if they don't already exist""" """Take a list and ListItems as JSON and
create DB entries if they don't already exist"""
book = models.Edition.objects.get(id=book_id) book = models.Edition.objects.get(id=book_id)
@ -408,7 +411,7 @@ def update_user_settings(user, data):
@app.task(queue=IMPORTS, base=SubTask) @app.task(queue=IMPORTS, base=SubTask)
def update_user_settings_task(job_id, child_id): def update_user_settings_task(job_id):
"""wrapper task for user's settings import""" """wrapper task for user's settings import"""
parent_job = BookwyrmImportJob.objects.get(id=job_id) parent_job = BookwyrmImportJob.objects.get(id=job_id)
@ -433,7 +436,7 @@ def update_goals(user, data):
@app.task(queue=IMPORTS, base=SubTask) @app.task(queue=IMPORTS, base=SubTask)
def update_goals_task(job_id, child_id): def update_goals_task(job_id):
"""wrapper task for user's goals import""" """wrapper task for user's goals import"""
parent_job = BookwyrmImportJob.objects.get(id=job_id) parent_job = BookwyrmImportJob.objects.get(id=job_id)
@ -450,7 +453,7 @@ def upsert_saved_lists(user, values):
@app.task(queue=IMPORTS, base=SubTask) @app.task(queue=IMPORTS, base=SubTask)
def upsert_saved_lists_task(job_id, child_id): def upsert_saved_lists_task(job_id):
"""wrapper task for user's saved lists import""" """wrapper task for user's saved lists import"""
parent_job = BookwyrmImportJob.objects.get(id=job_id) parent_job = BookwyrmImportJob.objects.get(id=job_id)
@ -477,7 +480,7 @@ def upsert_follows(user, values):
@app.task(queue=IMPORTS, base=SubTask) @app.task(queue=IMPORTS, base=SubTask)
def upsert_follows_task(job_id, child_id): def upsert_follows_task(job_id):
"""wrapper task for user's follows import""" """wrapper task for user's follows import"""
parent_job = BookwyrmImportJob.objects.get(id=job_id) parent_job = BookwyrmImportJob.objects.get(id=job_id)
@ -504,7 +507,7 @@ def upsert_user_blocks(user, user_ids):
@app.task(queue=IMPORTS, base=SubTask) @app.task(queue=IMPORTS, base=SubTask)
def upsert_user_blocks_task(job_id, child_id): def upsert_user_blocks_task(job_id):
"""wrapper task for user's blocks import""" """wrapper task for user's blocks import"""
parent_job = BookwyrmImportJob.objects.get(id=job_id) parent_job = BookwyrmImportJob.objects.get(id=job_id)

View file

@ -31,6 +31,8 @@ class Job(models.Model):
) )
class Meta: class Meta:
"""Make it abstract"""
abstract = True abstract = True
def complete_job(self): def complete_job(self):
@ -119,7 +121,7 @@ class ParentJob(Job):
if not self.complete and self.has_completed: if not self.complete and self.has_completed:
self.complete_job() self.complete_job()
def __terminate_job(self): def __terminate_job(self): # pylint: disable=unused-private-member
"""Tell workers to ignore and not execute this task """Tell workers to ignore and not execute this task
& pending child tasks. Extend. & pending child tasks. Extend.
""" """
@ -183,7 +185,9 @@ class ParentTask(app.Task):
Usage e.g. @app.task(base=ParentTask) Usage e.g. @app.task(base=ParentTask)
""" """
def before_start(self, task_id, args, kwargs): def before_start(
self, task_id, args, kwargs
): # pylint: disable=no-self-use, unused-argument
"""Handler called before the task starts. Override. """Handler called before the task starts. Override.
Prepare ParentJob before the task starts. Prepare ParentJob before the task starts.
@ -208,7 +212,9 @@ class ParentTask(app.Task):
if kwargs["no_children"]: if kwargs["no_children"]:
job.set_status(ChildJob.Status.ACTIVE) job.set_status(ChildJob.Status.ACTIVE)
def on_success(self, retval, task_id, args, kwargs): def on_success(
self, retval, task_id, args, kwargs
): # pylint: disable=no-self-use, unused-argument
"""Run by the worker if the task executes successfully. Override. """Run by the worker if the task executes successfully. Override.
Update ParentJob on Task complete. Update ParentJob on Task complete.
@ -241,7 +247,9 @@ class SubTask(app.Task):
Usage e.g. @app.task(base=SubTask) Usage e.g. @app.task(base=SubTask)
""" """
def before_start(self, task_id, args, kwargs): def before_start(
self, task_id, args, kwargs
): # pylint: disable=no-self-use, unused-argument
"""Handler called before the task starts. Override. """Handler called before the task starts. Override.
Prepare ChildJob before the task starts. Prepare ChildJob before the task starts.
@ -263,7 +271,9 @@ class SubTask(app.Task):
child_job.save(update_fields=["task_id"]) child_job.save(update_fields=["task_id"])
child_job.set_status(ChildJob.Status.ACTIVE) child_job.set_status(ChildJob.Status.ACTIVE)
def on_success(self, retval, task_id, args, kwargs): def on_success(
self, retval, task_id, args, kwargs
): # pylint: disable=no-self-use, unused-argument
"""Run by the worker if the task executes successfully. Override. """Run by the worker if the task executes successfully. Override.
Notify ChildJob of task completion. Notify ChildJob of task completion.

View file

@ -1,6 +1,7 @@
""" alert a user to activity """ """ alert a user to activity """
from django.db import models, transaction from django.db import models, transaction
from django.dispatch import receiver from django.dispatch import receiver
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
from . import ( from . import (
Boost, Boost,
@ -10,7 +11,6 @@ from . import (
BookwyrmImportJob, BookwyrmImportJob,
LinkDomain, LinkDomain,
) )
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
from . import ListItem, Report, Status, User, UserFollowRequest from . import ListItem, Report, Status, User, UserFollowRequest

View file

@ -141,7 +141,7 @@
<div class="table-container block content"> <div class="table-container block content">
<table class="table is-striped is-fullwidth"> <table class="table is-striped is-fullwidth">
<tr> <tr>
{% url 'settings-imports' status as url %} {% url 'settings-imports' status as url %}
<th> <th>
{% trans "ID" %} {% trans "ID" %}
</th> </th>
@ -231,7 +231,7 @@
<div class="table-container block content"> <div class="table-container block content">
<table class="table is-striped is-fullwidth"> <table class="table is-striped is-fullwidth">
<tr> <tr>
{% url 'settings-imports' status as url %} {% url 'settings-imports' status as url %}
<th> <th>
{% trans "ID" %} {% trans "ID" %}
</th> </th>
@ -299,5 +299,5 @@
</div> </div>
{% include 'snippets/pagination.html' with page=user_imports path=request.path %} {% include 'snippets/pagination.html' with page=user_imports path=request.path %}
{% endblock %}
</div> </div>
{% endblock %}

View file

@ -1,3 +1,4 @@
"""test bookwyrm user export functions"""
import datetime import datetime
import time import time
import json import json
@ -110,7 +111,7 @@ class BookwyrmExport(TestCase):
) )
# add to list # add to list
item = models.ListItem.objects.create( models.ListItem.objects.create(
book_list=self.list, book_list=self.list,
user=self.local_user, user=self.local_user,
book=self.edition, book=self.edition,
@ -226,7 +227,7 @@ class BookwyrmExport(TestCase):
json_data["books"][0]["quotes"][0]["quote"], "A rose by any other name" json_data["books"][0]["quotes"][0]["quote"], "A rose by any other name"
) )
def test_tar_export(self): def test_tar_export(self): # pylint: disable=unnecessary-pass
"""test the tar export function""" """test the tar export function"""
# TODO # TODO

View file

@ -5,14 +5,12 @@ import pathlib
from unittest.mock import patch from unittest.mock import patch
from django.db.models import Q from django.db.models import Q
from django.utils import timezone
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
from django.test import TestCase from django.test import TestCase
from bookwyrm import models from bookwyrm import models
from bookwyrm.settings import DOMAIN
from bookwyrm.utils.tar import BookwyrmTarFile from bookwyrm.utils.tar import BookwyrmTarFile
import bookwyrm.models.bookwyrm_import_job as bookwyrm_import_job from bookwyrm.models import bookwyrm_import_job
class BookwyrmImport(TestCase): class BookwyrmImport(TestCase):
@ -246,7 +244,9 @@ class BookwyrmImport(TestCase):
self.assertEqual(author.name, "James C. Scott") self.assertEqual(author.name, "James C. Scott")
def test_get_or_create_edition_existing(self): def test_get_or_create_edition_existing(self):
"""Test take a JSON string of books and editions, find or create the editions in the database and return a list of edition instances""" """Test take a JSON string of books and editions,
find or create the editions in the database and
return a list of edition instances"""
self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1)
self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1)
@ -258,7 +258,9 @@ class BookwyrmImport(TestCase):
self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1)
def test_get_or_create_edition_not_existing(self): def test_get_or_create_edition_not_existing(self):
"""Test take a JSON string of books and editions, find or create the editions in the database and return a list of edition instances""" """Test take a JSON string of books and editions,
find or create the editions in the database and
return a list of edition instances"""
self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1)
@ -441,7 +443,8 @@ class BookwyrmImport(TestCase):
) )
def test_upsert_list_existing(self): def test_upsert_list_existing(self):
"""Take a list and ListItems as JSON and create DB entries if they don't already exist""" """Take a list and ListItems as JSON and create DB entries
if they don't already exist"""
book_data = self.import_data["books"][0] book_data = self.import_data["books"][0]
@ -456,7 +459,7 @@ class BookwyrmImport(TestCase):
name="my list of books", user=self.local_user name="my list of books", user=self.local_user
) )
list_item = models.ListItem.objects.create( models.ListItem.objects.create(
book=self.book, book_list=book_list, user=self.local_user, order=1 book=self.book, book_list=book_list, user=self.local_user, order=1
) )
@ -489,7 +492,8 @@ class BookwyrmImport(TestCase):
) )
def test_upsert_list_not_existing(self): def test_upsert_list_not_existing(self):
"""Take a list and ListItems as JSON and create DB entries if they don't already exist""" """Take a list and ListItems as JSON and create DB entries
if they don't already exist"""
book_data = self.import_data["books"][0] book_data = self.import_data["books"][0]

View file

@ -1,5 +1,4 @@
""" test for app action functionality """ """ test for user export app functionality """
from collections import namedtuple
from unittest.mock import patch from unittest.mock import patch
from django.http import HttpResponse from django.http import HttpResponse

View file

@ -1,12 +1,15 @@
"""manage tar files for user exports"""
import io
import tarfile
from uuid import uuid4 from uuid import uuid4
from django.core.files import File from django.core.files import File
import tarfile
import io
class BookwyrmTarFile(tarfile.TarFile): class BookwyrmTarFile(tarfile.TarFile):
def write_bytes(self, data: bytes, filename="archive.json"): """Create tar files for user exports"""
"""Add a file containing :data: bytestring with name :filename: to the archive"""
def write_bytes(self, data: bytes):
"""Add a file containing bytes to the archive"""
buffer = io.BytesIO(data) buffer = io.BytesIO(data)
info = tarfile.TarInfo("archive.json") info = tarfile.TarInfo("archive.json")
info.size = len(data) info.size = len(data)
@ -30,10 +33,12 @@ class BookwyrmTarFile(tarfile.TarFile):
self.addfile(info, fileobj=image) self.addfile(info, fileobj=image)
def read(self, filename): def read(self, filename):
"""read data from the tar"""
with self.extractfile(filename) as reader: with self.extractfile(filename) as reader:
return reader.read() return reader.read()
def write_image_to_file(self, filename, file_field): def write_image_to_file(self, filename, file_field):
"""add an image to the tar"""
extension = filename.rsplit(".")[-1] extension = filename.rsplit(".")[-1]
with self.extractfile(filename) as reader: with self.extractfile(filename) as reader:
filename = f"{str(uuid4())}.{extension}" filename = f"{str(uuid4())}.{extension}"

View file

@ -13,7 +13,7 @@ from django.views import View
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.shortcuts import redirect from django.shortcuts import redirect
from bookwyrm import models, settings from bookwyrm import models
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
@ -135,10 +135,11 @@ class ExportUser(View):
@method_decorator(login_required, name="dispatch") @method_decorator(login_required, name="dispatch")
class ExportArchive(View): class ExportArchive(View): # pylint: disable=line-too-long
"""Serve the archive file""" """Serve the archive file"""
def get(self, request, archive_id): def get(self, request, archive_id):
"""download user export file"""
export = BookwyrmExportJob.objects.get(task_id=archive_id, user=request.user) export = BookwyrmExportJob.objects.get(task_id=archive_id, user=request.user)
return HttpResponse( return HttpResponse(
export.export_data, export.export_data,