Skip to content

Commit f0af798

Browse files
committed
Merge branch 'main' into production
2 parents 21f65ca + 53c8085 commit f0af798

File tree

81 files changed

+15878
-301
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+15878
-301
lines changed

.github/workflows/mypy.yml

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Mypy
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v3
16+
- name: Set up Python 3.9
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: 3.9
20+
- name: Install Dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install -r requirements.txt
24+
- name: Analysing the code with mypy
25+
env:
26+
SECRET_KEY: beepbeep
27+
DEBUG: false
28+
USE_HTTPS: true
29+
DOMAIN: your.domain.here
30+
BOOKWYRM_DATABASE_BACKEND: postgres
31+
MEDIA_ROOT: images/
32+
POSTGRES_PASSWORD: hunter2
33+
POSTGRES_USER: postgres
34+
POSTGRES_DB: github_actions
35+
POSTGRES_HOST: 127.0.0.1
36+
CELERY_BROKER: ""
37+
REDIS_BROKER_PORT: 6379
38+
REDIS_BROKER_PASSWORD: beep
39+
USE_DUMMY_CACHE: true
40+
FLOWER_PORT: 8888
41+
EMAIL_HOST: "smtp.mailgun.org"
42+
EMAIL_PORT: 587
43+
EMAIL_HOST_USER: ""
44+
EMAIL_HOST_PASSWORD: ""
45+
EMAIL_USE_TLS: true
46+
ENABLE_PREVIEW_IMAGES: false
47+
ENABLE_THUMBNAIL_GENERATION: true
48+
HTTP_X_FORWARDED_PROTO: false
49+
run: |
50+
mypy bookwyrm celerywyrm

bookwyrm/activitypub/base_activity.py

+50-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from dataclasses import dataclass, fields, MISSING
33
from json import JSONEncoder
44
import logging
5+
from typing import Optional, Union, TypeVar, overload, Any
6+
57
import requests
68

79
from django.apps import apps
@@ -10,12 +12,15 @@
1012

1113
from bookwyrm import models
1214
from bookwyrm.connectors import ConnectorException, get_data
15+
from bookwyrm.models import base_model
1316
from bookwyrm.signatures import make_signature
1417
from bookwyrm.settings import DOMAIN, INSTANCE_ACTOR_USERNAME
1518
from bookwyrm.tasks import app, MISC
1619

1720
logger = logging.getLogger(__name__)
1821

22+
TBookWyrmModel = TypeVar("TBookWyrmModel", bound=base_model.BookWyrmModel)
23+
1924

2025
class ActivitySerializerError(ValueError):
2126
"""routine problems serializing activitypub json"""
@@ -65,7 +70,11 @@ class ActivityObject:
6570
id: str
6671
type: str
6772

68-
def __init__(self, activity_objects=None, **kwargs):
73+
def __init__(
74+
self,
75+
activity_objects: Optional[list[str, base_model.BookWyrmModel]] = None,
76+
**kwargs: dict[str, Any],
77+
):
6978
"""this lets you pass in an object with fields that aren't in the
7079
dataclass, which it ignores. Any field in the dataclass is required or
7180
has a default value"""
@@ -101,13 +110,13 @@ def __init__(self, activity_objects=None, **kwargs):
101110
# pylint: disable=too-many-locals,too-many-branches,too-many-arguments
102111
def to_model(
103112
self,
104-
model=None,
105-
instance=None,
106-
allow_create=True,
107-
save=True,
108-
overwrite=True,
109-
allow_external_connections=True,
110-
):
113+
model: Optional[type[TBookWyrmModel]] = None,
114+
instance: Optional[TBookWyrmModel] = None,
115+
allow_create: bool = True,
116+
save: bool = True,
117+
overwrite: bool = True,
118+
allow_external_connections: bool = True,
119+
) -> Optional[TBookWyrmModel]:
111120
"""convert from an activity to a model instance. Args:
112121
model: the django model that this object is being converted to
113122
(will guess if not known)
@@ -296,14 +305,40 @@ def get_model_from_type(activity_type):
296305

297306

298307
# pylint: disable=too-many-arguments
308+
@overload
299309
def resolve_remote_id(
300-
remote_id,
301-
model=None,
302-
refresh=False,
303-
save=True,
304-
get_activity=False,
305-
allow_external_connections=True,
306-
):
310+
remote_id: str,
311+
model: type[TBookWyrmModel],
312+
refresh: bool = False,
313+
save: bool = True,
314+
get_activity: bool = False,
315+
allow_external_connections: bool = True,
316+
) -> TBookWyrmModel:
317+
...
318+
319+
320+
# pylint: disable=too-many-arguments
321+
@overload
322+
def resolve_remote_id(
323+
remote_id: str,
324+
model: Optional[str] = None,
325+
refresh: bool = False,
326+
save: bool = True,
327+
get_activity: bool = False,
328+
allow_external_connections: bool = True,
329+
) -> base_model.BookWyrmModel:
330+
...
331+
332+
333+
# pylint: disable=too-many-arguments
334+
def resolve_remote_id(
335+
remote_id: str,
336+
model: Optional[Union[str, type[base_model.BookWyrmModel]]] = None,
337+
refresh: bool = False,
338+
save: bool = True,
339+
get_activity: bool = False,
340+
allow_external_connections: bool = True,
341+
) -> base_model.BookWyrmModel:
307342
"""take a remote_id and return an instance, creating if necessary. Args:
308343
remote_id: the unique url for looking up the object in the db or by http
309344
model: a string or object representing the model that corresponds to the object

bookwyrm/activitypub/book.py

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
""" book and author data """
22
from dataclasses import dataclass, field
3-
from typing import List
3+
from typing import Optional
44

55
from .base_activity import ActivityObject
66
from .image import Document
@@ -11,19 +11,19 @@
1111
class BookData(ActivityObject):
1212
"""shared fields for all book data and authors"""
1313

14-
openlibraryKey: str = None
15-
inventaireId: str = None
16-
librarythingKey: str = None
17-
goodreadsKey: str = None
18-
bnfId: str = None
19-
viaf: str = None
20-
wikidata: str = None
21-
asin: str = None
22-
aasin: str = None
23-
isfdb: str = None
24-
lastEditedBy: str = None
25-
links: List[str] = field(default_factory=lambda: [])
26-
fileLinks: List[str] = field(default_factory=lambda: [])
14+
openlibraryKey: Optional[str] = None
15+
inventaireId: Optional[str] = None
16+
librarythingKey: Optional[str] = None
17+
goodreadsKey: Optional[str] = None
18+
bnfId: Optional[str] = None
19+
viaf: Optional[str] = None
20+
wikidata: Optional[str] = None
21+
asin: Optional[str] = None
22+
aasin: Optional[str] = None
23+
isfdb: Optional[str] = None
24+
lastEditedBy: Optional[str] = None
25+
links: list[str] = field(default_factory=list)
26+
fileLinks: list[str] = field(default_factory=list)
2727

2828

2929
# pylint: disable=invalid-name
@@ -35,17 +35,17 @@ class Book(BookData):
3535
sortTitle: str = None
3636
subtitle: str = None
3737
description: str = ""
38-
languages: List[str] = field(default_factory=lambda: [])
38+
languages: list[str] = field(default_factory=list)
3939
series: str = ""
4040
seriesNumber: str = ""
41-
subjects: List[str] = field(default_factory=lambda: [])
42-
subjectPlaces: List[str] = field(default_factory=lambda: [])
41+
subjects: list[str] = field(default_factory=list)
42+
subjectPlaces: list[str] = field(default_factory=list)
4343

44-
authors: List[str] = field(default_factory=lambda: [])
44+
authors: list[str] = field(default_factory=list)
4545
firstPublishedDate: str = ""
4646
publishedDate: str = ""
4747

48-
cover: Document = None
48+
cover: Optional[Document] = None
4949
type: str = "Book"
5050

5151

@@ -58,10 +58,10 @@ class Edition(Book):
5858
isbn10: str = ""
5959
isbn13: str = ""
6060
oclcNumber: str = ""
61-
pages: int = None
61+
pages: Optional[int] = None
6262
physicalFormat: str = ""
6363
physicalFormatDetail: str = ""
64-
publishers: List[str] = field(default_factory=lambda: [])
64+
publishers: list[str] = field(default_factory=list)
6565
editionRank: int = 0
6666

6767
type: str = "Edition"
@@ -73,7 +73,7 @@ class Work(Book):
7373
"""work instance of a book object"""
7474

7575
lccn: str = ""
76-
editions: List[str] = field(default_factory=lambda: [])
76+
editions: list[str] = field(default_factory=list)
7777
type: str = "Work"
7878

7979

@@ -83,12 +83,12 @@ class Author(BookData):
8383
"""author of a book"""
8484

8585
name: str
86-
isni: str = None
87-
viafId: str = None
88-
gutenbergId: str = None
89-
born: str = None
90-
died: str = None
91-
aliases: List[str] = field(default_factory=lambda: [])
86+
isni: Optional[str] = None
87+
viafId: Optional[str] = None
88+
gutenbergId: Optional[str] = None
89+
born: Optional[str] = None
90+
died: Optional[str] = None
91+
aliases: list[str] = field(default_factory=list)
9292
bio: str = ""
9393
wikipediaLink: str = ""
9494
type: str = "Author"

bookwyrm/activitystreams.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,9 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
329329
remove_status_task.delay(instance.id)
330330
return
331331

332-
# To avoid creating a zillion unnecessary tasks caused by re-saving the model,
333-
# check if it's actually ready to send before we go. We're trusting this was
334-
# set correctly by the inbox or view
335-
if not instance.ready:
332+
# We don't want to create multiple add_status_tasks for each status, and because
333+
# the transactions are atomic, on_commit won't run until the status is ready to add.
334+
if not created:
336335
return
337336

338337
# when creating new things, gotta wait on the transaction
@@ -343,6 +342,10 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
343342

344343
def add_status_on_create_command(sender, instance, created):
345344
"""runs this code only after the database commit completes"""
345+
# boosts trigger 'saves" twice, so don't bother duplicating the task
346+
if sender == models.Boost and not created:
347+
return
348+
346349
priority = STREAMS
347350
# check if this is an old status, de-prioritize if so
348351
# (this will happen if federation is very slow, or, more expectedly, on csv import)

bookwyrm/book_search.py

+44-9
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,53 @@
11
""" using a bookwyrm instance as a source of book data """
2+
from __future__ import annotations
23
from dataclasses import asdict, dataclass
34
from functools import reduce
45
import operator
6+
from typing import Optional, Union, Any, Literal, overload
57

68
from django.contrib.postgres.search import SearchRank, SearchQuery
79
from django.db.models import F, Q
10+
from django.db.models.query import QuerySet
811

912
from bookwyrm import models
1013
from bookwyrm import connectors
1114
from bookwyrm.settings import MEDIA_FULL_URL
1215

1316

17+
@overload
18+
def search(
19+
query: str,
20+
*,
21+
min_confidence: float = 0,
22+
filters: Optional[list[Any]] = None,
23+
return_first: Literal[False],
24+
) -> QuerySet[models.Edition]:
25+
...
26+
27+
28+
@overload
29+
def search(
30+
query: str,
31+
*,
32+
min_confidence: float = 0,
33+
filters: Optional[list[Any]] = None,
34+
return_first: Literal[True],
35+
) -> Optional[models.Edition]:
36+
...
37+
38+
1439
# pylint: disable=arguments-differ
15-
def search(query, min_confidence=0, filters=None, return_first=False):
40+
def search(
41+
query: str,
42+
*,
43+
min_confidence: float = 0,
44+
filters: Optional[list[Any]] = None,
45+
return_first: bool = False,
46+
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
1647
"""search your local database"""
1748
filters = filters or []
1849
if not query:
19-
return []
50+
return None if return_first else []
2051
query = query.strip()
2152

2253
results = None
@@ -66,7 +97,9 @@ def format_search_result(search_result):
6697
).json()
6798

6899

69-
def search_identifiers(query, *filters, return_first=False):
100+
def search_identifiers(
101+
query, *filters, return_first=False
102+
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
70103
"""tries remote_id, isbn; defined as dedupe fields on the model"""
71104
if connectors.maybe_isbn(query):
72105
# Oh did you think the 'S' in ISBN stood for 'standard'?
@@ -87,7 +120,9 @@ def search_identifiers(query, *filters, return_first=False):
87120
return results
88121

89122

90-
def search_title_author(query, min_confidence, *filters, return_first=False):
123+
def search_title_author(
124+
query, min_confidence, *filters, return_first=False
125+
) -> QuerySet[models.Edition]:
91126
"""searches for title and author"""
92127
query = SearchQuery(query, config="simple") | SearchQuery(query, config="english")
93128
results = (
@@ -122,11 +157,11 @@ class SearchResult:
122157
title: str
123158
key: str
124159
connector: object
125-
view_link: str = None
126-
author: str = None
127-
year: str = None
128-
cover: str = None
129-
confidence: int = 1
160+
view_link: Optional[str] = None
161+
author: Optional[str] = None
162+
year: Optional[str] = None
163+
cover: Optional[str] = None
164+
confidence: float = 1.0
130165

131166
def __repr__(self):
132167
# pylint: disable=consider-using-f-string

0 commit comments

Comments
 (0)