Skip to content

Commit

Permalink
Add support for Series AlternativeNames
Browse files Browse the repository at this point in the history
  • Loading branch information
bpepple committed Sep 17, 2024
1 parent 8b47bc8 commit 5a49957
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 4 deletions.
57 changes: 57 additions & 0 deletions darkseid/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,61 @@ class Role(Basic):
primary: bool = False


@dataclass
class AlternativeNames(Basic):
"""
A data class representing an alternative name for a series with basic information and validations.
Attributes:
name (str): The alternative name for a series.
id_ (int | None): The ID associated with the alternative name, defaults to None.
language (str | None): The 2-letter ISO code of the language, defaults to None.
Static Methods:
validate_language(value: str, **_: any) -> str | None: Validates a language value.
"""

language: str | None = None

@staticmethod
def validate_language(value: str, **_: any) -> str | None:
"""
Validates a language value.
If the value is empty, it returns None. Otherwise, it strips any leading or trailing
whitespace from the value. If the length of the value is 2, it tries to find the
language object using the alpha-2 code. Otherwise, it tries to look up the language
object using the value. If the language object is not found, it raises a ValueError.
Args:
value (str): The language value to validate.
**_ (any): Additional keyword arguments (ignored).
Returns:
Optional[str]: The validated language code, or None if the value is empty.
Raises:
ValueError: Raised when the language object cannot be found.
"""

if not value:
return None
value = value.strip()

if len(value) == COUNTRY_LEN:
obj = pycountry.languages.get(alpha_2=value)
else:
try:
obj = pycountry.languages.lookup(value)
except LookupError as e:
msg = f"Couldn't find language {value}"
raise ValueError(msg) from e
if obj is None:
msg = f"Couldn't find language {value}"
raise ValueError(msg)
return obj.alpha_2


@dataclass
class Series(Basic, Validations):
"""
Expand All @@ -200,6 +255,7 @@ class Series(Basic, Validations):
sort_name (str | None): The sort name of the series, defaults to None.
volume (int | None): The volume of the series, defaults to None.
format (str | None): The format of the series, defaults to None.
alternative_names: list[AlternativeNames]: A list of alternative names for series.
language (str | None): The 2-letter ISO code of the language, defaults to None.
Static Methods:
Expand All @@ -209,6 +265,7 @@ class Series(Basic, Validations):
sort_name: str | None = None
volume: int | None = None
format: str | None = None
alternative_names: list[AlternativeNames] = field(default_factory=list)
language: str | None = None # 2-letter iso code

@staticmethod
Expand Down
23 changes: 23 additions & 0 deletions darkseid/metroninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from darkseid.issue_string import IssueString
from darkseid.metadata import (
GTIN,
AlternativeNames,
Arc,
Basic,
Credit,
Expand Down Expand Up @@ -220,6 +221,18 @@ def assign_series(root: ET.Element, series: Series) -> None:
ET.SubElement(series_node, "Format").text = (
series.format if series.format in MetronInfo.mix_series_format else "Single Issue"
)
if series.alternative_names:
alt_names_node = ET.SubElement(series_node, "AlternativeNames")
for alt_name in series.alternative_names:
name_node = ET.SubElement(alt_names_node, "Name")
name_node.text = alt_name.name
alt_attrib = {}
if alt_name.id_:
alt_attrib["id"] = str(alt_name.id_)
if alt_name.language:
alt_attrib["lang"] = alt_name.language
if alt_attrib:
name_node.attrib = alt_attrib

@staticmethod
def assign_info_source(root: ET.Element, primary: Basic, alt_lst: list[Basic]) -> None:
Expand Down Expand Up @@ -406,6 +419,14 @@ def get_prices() -> list[Price]:
Price(Decimal(item.text), item.attrib.get("country", "US")) for item in resource
]

def _create_alt_name_list(element: ET.Element) -> list[AlternativeNames]:
return [
AlternativeNames(
name.text, get_id_from_attrib(name.attrib), name.attrib.get("lang")
)
for name in element.findall("Name")
]

def get_series() -> Series | None:
resource = root.find("Series")
if resource is None:
Expand All @@ -427,6 +448,8 @@ def get_series() -> Series | None:
series_md.volume = int(item.text)
case "Format":
series_md.format = item.text
case "AlternativeNames":
series_md.alternative_names = _create_alt_name_list(item)
case _:
pass

Expand Down
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from darkseid.metadata import Arc, Basic, Metadata, Price, Series, Universe
from darkseid.metadata import AlternativeNames, Arc, Basic, Metadata, Price, Series, Universe


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -38,7 +38,13 @@ def fake_metadata() -> Metadata:
@pytest.fixture(scope="session")
def fake_overlay_metadata() -> Metadata:
overlay_md = Metadata()
overlay_md.series = Series(name="Aquaman", sort_name="Aquaman", volume=1, format="Annual")
overlay_md.series = Series(
name="Aquaman",
sort_name="Aquaman",
volume=1,
format="Annual",
alternative_names=[AlternativeNames("Water Boy"), AlternativeNames("Fishy", 60, "de")],
)
overlay_md.cover_date = date(1994, 10, 1)
overlay_md.reprints = [Basic("Aquaman (1964) #64", 12345)]
overlay_md.prices = [Price(Decimal("3.99")), Price(Decimal("1.5"), "CA")]
Expand Down
17 changes: 17 additions & 0 deletions tests/test_files/MetronInfo.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,28 @@
<xs:element name="SortName" type="xs:string" minOccurs="0" />
<xs:element name="Volume" type="xs:int" minOccurs="0" />
<xs:element name="Format" type="formatType" minOccurs="0" />
<xs:element name="AlternativeNames" type="alternativeNameType" minOccurs="0" />
</xs:all>
<xs:attribute name="lang" type="languageCode" default="en" />
<xs:attribute name="id" type="xs:positiveInteger" />
</xs:complexType>

<xs:complexType name="alternativeNameType">
<xs:sequence>
<xs:element name="Name" type="nameType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>

<!-- Alternative Series Name Base type -->
<xs:complexType name="nameType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:positiveInteger" />
<xs:attribute name="lang" type="languageCode" default="en" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="charactersType">
<xs:sequence>
<xs:element name="Character" type="resourceType" minOccurs="0" maxOccurs="unbounded" />
Expand Down
4 changes: 4 additions & 0 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def test_metadata_overlay(fake_metadata: Metadata, fake_overlay_metadata: Metada
md.overlay(fake_overlay_metadata)

assert md.series.name == "Aquaman"
assert len(md.series.alternative_names) == 2
assert md.series.alternative_names[1].name == "Fishy"
assert md.series.alternative_names[1].id_ == 60
assert md.series.alternative_names[1].language == "de"
assert md.issue == "0"
assert md.stories == fake_metadata.stories
assert md.cover_date == date(1994, 10, 1)
Expand Down
34 changes: 32 additions & 2 deletions tests/test_metroninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@
import pytest
from lxml import etree

from darkseid.metadata import GTIN, Arc, Basic, Credit, Metadata, Price, Role, Series, Universe
from darkseid.metadata import (
GTIN,
AlternativeNames,
Arc,
Basic,
Credit,
Metadata,
Price,
Role,
Series,
Universe,
)
from darkseid.metroninfo import MetronInfo

MI_XSD = "tests/test_files/MetronInfo.xsd"
Expand Down Expand Up @@ -101,7 +112,17 @@ def test_convert_metadata_to_xml(metron_info):
info_source=Basic("Metron", id_=54),
alt_sources=[Basic("Comic Vine", id_=90)],
publisher=Basic("Marvel", id_=1),
series=Series(name="Spider-Man", volume=1, format="Single Issue", id_=50, language="en"),
series=Series(
name="Spider-Man",
volume=1,
format="Single Issue",
id_=50,
language="en",
alternative_names=[
AlternativeNames("Bug Boy", 50),
AlternativeNames("Spider", language="de"),
],
),
issue="50",
story_arcs=[Arc("Final Crisis, Inc", id_=80, number=1)],
cover_date=date(2020, 1, 1),
Expand Down Expand Up @@ -141,6 +162,10 @@ def test_metadata_from_string(metron_info):
<SortName>Spider-Man</SortName>
<Volume>1</Volume>
<Format>Omnibus</Format>
<AlternativeNames>
<Name id="1234">Foo</Name>
<Name lang="de">Hüsker Dü</Name>
</AlternativeNames>
</Series>
<Prices>
<Price country="US">3.99</Price>
Expand Down Expand Up @@ -183,6 +208,11 @@ def test_metadata_from_string(metron_info):
assert result.publisher.name == "Marvel"
assert result.series.name == "Spider-Man"
assert result.series.format == "Omnibus"
assert len(result.series.alternative_names) == 2
assert result.series.alternative_names[0].name == "Foo"
assert result.series.alternative_names[0].id_ == 1234
assert result.series.alternative_names[1].language == "de"
assert result.series.alternative_names[1].name == "Hüsker Dü"
assert result.prices[0].amount == Decimal("3.99")
assert result.gtin.isbn == 1234567890123
assert result.gtin.upc == 76194130593600111
Expand Down

0 comments on commit 5a49957

Please sign in to comment.