Skip to content

Commit

Permalink
Refactor betty.wikipedia's Image and Summary into dataclasses (#2082)
Browse files Browse the repository at this point in the history
  • Loading branch information
bartfeenstra authored Oct 6, 2024
1 parent e4dabe0 commit 2f48d2f
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 161 deletions.
3 changes: 3 additions & 0 deletions betty/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
overload,
cast,
TypeAlias,
final,
)

from betty.assertion.error import AssertionFailedGroup, AssertionFailed, Key, Index
Expand Down Expand Up @@ -98,6 +99,7 @@ class _Field(Generic[_AssertionValueT, _AssertionReturnT]):
assertion: Assertion[_AssertionValueT, _AssertionReturnT] | None = None


@final
@dataclass(frozen=True)
class RequiredField(
Generic[_AssertionValueT, _AssertionReturnT],
Expand All @@ -110,6 +112,7 @@ class RequiredField(
pass # pragma: no cover


@final
@dataclass(frozen=True)
class OptionalField(
Generic[_AssertionValueT, _AssertionReturnT],
Expand Down
3 changes: 2 additions & 1 deletion betty/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ async def make_context(
return ctx


@dataclass
@final
@dataclass(frozen=True)
class ContextAppObject:
"""
The running Betty application and it localizer.
Expand Down
5 changes: 3 additions & 2 deletions betty/fetch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dataclasses import dataclass
from json import loads
from pathlib import Path
from typing import Any
from typing import Any, final

from multidict import CIMultiDict

Expand All @@ -21,7 +21,8 @@ class FetchError(UserFacingError, RuntimeError):
pass # pragma: no cover


@dataclass
@final
@dataclass(frozen=True)
class FetchResponse:
"""
An HTTP response.
Expand Down
3 changes: 2 additions & 1 deletion betty/gramps/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from enum import Enum
from logging import getLogger
from pathlib import Path
from typing import Iterable, Any, IO, cast, TYPE_CHECKING, TypeVar, Generic
from typing import Iterable, Any, IO, cast, TYPE_CHECKING, TypeVar, Generic, final
from xml.etree import ElementTree

import aiofiles
Expand Down Expand Up @@ -112,6 +112,7 @@ class GrampsEntityType(Enum):
SOURCE = "source"


@final
@dataclass(frozen=True)
class GrampsEntityReference:
"""
Expand Down
2 changes: 1 addition & 1 deletion betty/project/extension/cotton_candy/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class _SourceIndexer(_EntityTypeIndexer[Source]):


@final
@dataclass
@dataclass(frozen=True)
class _Entry:
text: set[str]
result: str
Expand Down
18 changes: 17 additions & 1 deletion betty/tests/coverage/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,13 @@ class TestKnownToBeMissing:
# This is inherited from @dataclass.
"__eq__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__delattr__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__hash__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__replace__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__setattr__": TestKnownToBeMissing,
},
},
"betty/fetch/static.py": TestKnownToBeMissing,
Expand Down Expand Up @@ -608,11 +614,21 @@ class TestKnownToBeMissing:
},
"betty/warnings.py": TestKnownToBeMissing,
"betty/wikipedia/__init__.py": {
# This is a dataclass.
"Image": TestKnownToBeMissing,
# This is an empty class.
"NotAPageError": TestKnownToBeMissing,
"Summary": {
"name": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__eq__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__delattr__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__hash__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__replace__": TestKnownToBeMissing,
# This is inherited from @dataclass.
"__setattr__": TestKnownToBeMissing,
},
},
}
Expand Down
48 changes: 0 additions & 48 deletions betty/tests/wikipedia/test___init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,54 +91,6 @@ async def test_url(self) -> None:
sut = Summary("nl", "Amsterdam", "Title for Amsterdam", "Content for Amsterdam")
assert sut.url == "https://nl.wikipedia.org/wiki/Amsterdam"

async def test_title(self) -> None:
title = "Title for Amsterdam"
sut = Summary("nl", "Amsterdam", title, "Content for Amsterdam")
assert sut.title == title

async def test_content(self) -> None:
content = "Content for Amsterdam"
sut = Summary("nl", "Amsterdam", "Title for Amsterdam", content)
assert sut.content == content

@pytest.mark.parametrize(
("expected", "left", "right"),
[
(
True,
Summary("en", "name", "title", "content"),
Summary("en", "name", "title", "content"),
),
(
False,
Summary("en", "name", "title", "content"),
Summary("nl", "name", "title", "content"),
),
(
False,
Summary("en", "name", "title", "content"),
Summary("en", "not-a-name", "title", "content"),
),
(
False,
Summary("en", "name", "title", "content"),
Summary("en", "name", "not-a-title", "content"),
),
(
False,
Summary("en", "name", "title", "content"),
Summary("en", "name", "title", "not-a-content"),
),
(
False,
Summary("en", "name", "title", "content"),
123,
),
],
)
async def test___eq__(self, expected: bool, left: Summary, right: object) -> None:
assert (left == right) is expected


class TestRetriever:
@pytest.mark.parametrize(
Expand Down
127 changes: 20 additions & 107 deletions betty/wikipedia/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@
import re
from asyncio import gather
from collections import defaultdict
from collections.abc import (
Mapping,
)
from collections.abc import Mapping
from contextlib import suppress, contextmanager
from dataclasses import dataclass
from json import JSONDecodeError
from pathlib import Path
from typing import cast, Any, TYPE_CHECKING
from typing import cast, Any, TYPE_CHECKING, final
from urllib.parse import quote, urlparse

from geopy import Point

from betty.ancestry.file import File
from betty.ancestry.file_reference import FileReference
from betty.ancestry.has_file_references import HasFileReferences
Expand All @@ -36,20 +33,15 @@
)
from betty.locale.error import LocaleError
from betty.locale.localizable import plain
from betty.locale.localized import Localized
from betty.media_type import MediaType
from betty.media_type.media_types import HTML
from geopy import Point

if TYPE_CHECKING:
from betty.ancestry import Ancestry
from betty.locale.localizer import LocalizerRepository
from betty.fetch import Fetcher
from collections.abc import (
Sequence,
MutableSequence,
MutableMapping,
Iterator,
)
from collections.abc import Sequence, MutableSequence, MutableMapping, Iterator


class NotAPageError(ValueError):
Expand All @@ -70,117 +62,38 @@ def _parse_url(url: str) -> tuple[str, str]:
return cast(tuple[str, str], match.groups())


class Summary(Localized):
@final
@dataclass(frozen=True)
class Summary:
"""
A Wikipedia page summary.
"""

def __init__(self, locale: str, name: str, title: str, content: str):
self._name = name
self._title = title
self._content = content
self._locale = locale

def __eq__(self, other: object) -> bool:
if not isinstance(other, Summary):
return False
if self.name != other.name:
return False
if self.url != other.url:
return False
if self.title != other.title:
return False
if self.content != other.content:
return False
return True

@property
def name(self) -> str:
"""
The page's machine name.
"""
return self._name
locale: str
name: str
title: str
content: str

@property
def url(self) -> str:
"""
The URL to the web page.
"""
return f"https://{self.locale}.wikipedia.org/wiki/{self._name}"

@property
def title(self) -> str:
"""
The page's human-readable title.
"""
return self._title

@property
def content(self) -> str:
"""
The page's human-readable summary content.
"""
return self._content
return f"https://{self.locale}.wikipedia.org/wiki/{self.name}"


@final
@dataclass(frozen=True)
class Image:
"""
An image from Wikimedia Commons.
"""

def __init__(
self,
path: Path,
media_type: MediaType,
title: str,
wikimedia_commons_url: str,
name: str,
):
self._path = path
self._media_type = media_type
self._title = title
self._wikimedia_commons_url = wikimedia_commons_url
self._name = name

def __hash__(self) -> int:
return hash(
(self.path, self.media_type, self.title, self.wikimedia_commons_url)
)

@property
def path(self) -> Path:
"""
The path to the image on disk.
"""
return self._path

@property
def media_type(self) -> MediaType:
"""
The image's media type.
"""
return self._media_type

@property
def title(self) -> str:
"""
The human-readable image title.
"""
return self._title

@property
def wikimedia_commons_url(self) -> str:
"""
The URL to the Wikimedia Commons web page for this image.
"""
return self._wikimedia_commons_url

@property
def name(self) -> str:
"""
The image's file name.
"""
return self._name
path: Path
media_type: MediaType
title: str
wikimedia_commons_url: str
name: str


class _Retriever:
Expand Down

0 comments on commit 2f48d2f

Please sign in to comment.