Skip to content

Commit

Permalink
Add series and issue list searching
Browse files Browse the repository at this point in the history
Update tests
  • Loading branch information
Buried-In-Code committed Dec 16, 2024
1 parent b60beb6 commit 510a490
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 121 deletions.
52 changes: 45 additions & 7 deletions grayven/grand_comics_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from grayven import __version__
from grayven.exceptions import ServiceError
from grayven.schemas.issue import Issue
from grayven.schemas.issue import BasicIssue, Issue
from grayven.schemas.publisher import Publisher
from grayven.schemas.series import Series
from grayven.sqlite_cache import SQLiteCache
Expand Down Expand Up @@ -45,7 +45,12 @@ def _perform_get_request(
except RequestError as err:
raise ServiceError("Unable to connect to '%s'", url) from err
except HTTPStatusError as err:
raise ServiceError(err) from err
try:
if err.response.status_code == 404:
raise ServiceError(err.response.json()["detail"])
raise ServiceError(err) from err
except JSONDecodeError as err:
raise ServiceError("Unable to parse response from '%s' as Json", url) from err
except JSONDecodeError as err:
raise ServiceError("Unable to parse response from '%s' as Json", url) from err
except TimeoutException as err:
Expand All @@ -58,7 +63,7 @@ def _get_request(
params = {}
params["format"] = "json"

url = self.API_URL + endpoint
url = self.API_URL + endpoint + "/"
cache_params = f"?{urlencode({k: params[k] for k in sorted(params)})}"
cache_key = url + cache_params

Expand Down Expand Up @@ -87,9 +92,9 @@ def _get_paged_request(
results.extend(response["results"])
return results[:max_results]

def list_publishers(self) -> list[Publisher]:
def list_publishers(self, max_results: int = 500) -> list[Publisher]:
try:
results = self._get_paged_request(endpoint="/publisher")
results = self._get_paged_request(endpoint="/publisher", max_results=max_results)
return TypeAdapter(list[Publisher]).validate_python(results)
except ValidationError as err:
raise ServiceError(err) from err
Expand All @@ -101,9 +106,20 @@ def get_publisher(self, id: int) -> Optional[Publisher]: # noqa: A002
except ValidationError as err:
raise ServiceError(err) from err

def list_series(self) -> list[Series]:
def list_series(
self, name: Optional[str] = None, year: Optional[int] = None, max_results: int = 500
) -> list[Series]:
try:
results = self._get_paged_request(endpoint="/series")
if name is None:
results = self._get_paged_request(endpoint="/series", max_results=max_results)
elif year is None:
results = self._get_paged_request(
endpoint=f"/series/name/{name}", max_results=max_results
)
else:
results = self._get_paged_request(
endpoint=f"/series/name/{name}/year/{year}", max_results=max_results
)
return TypeAdapter(list[Series]).validate_python(results)
except ValidationError as err:
raise ServiceError(err) from err
Expand All @@ -115,6 +131,28 @@ def get_series(self, id: int) -> Optional[Series]: # noqa: A002
except ValidationError as err:
raise ServiceError(err) from err

def list_issues(
self,
series_name: str,
issue_number: int,
year: Optional[int] = None,
max_results: int = 500,
) -> list[BasicIssue]:
try:
if year is None:
results = self._get_paged_request(
endpoint=f"/series/name/{series_name}/issue/{issue_number}",
max_results=max_results,
)
else:
results = self._get_paged_request(
endpoint=f"/series/name/{series_name}/issue/{issue_number}/year/{year}",
max_results=max_results,
)
return TypeAdapter(list[BasicIssue]).validate_python(results)
except ValidationError as err:
raise ServiceError(err) from err

def get_issue(self, id: int) -> Optional[Issue]: # noqa: A002
try:
result = self._get_request(endpoint=f"/issue/{id}")
Expand Down
35 changes: 17 additions & 18 deletions grayven/schemas/issue.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__all__ = ["Genre", "Issue", "Story", "StoryType"]
__all__ = ["Issue", "Story", "StoryType"]

import re
from datetime import date
from enum import Enum
from typing import Optional

Expand All @@ -16,11 +17,6 @@ class StoryType(str, Enum):
ADVERTISEMENT = "advertisement"


class Genre(str, Enum):
SATIRE_PARODY = "satire-parody"
SUPERHERO = "superhero"


class Story(BaseModel):
type: StoryType
title: str # or Blank
Expand All @@ -34,36 +30,39 @@ class Story(BaseModel):
letters: str # or Blank
editing: str # or Blank
job_number: str # or Blank
genre: Optional[Genre]
genre: str # or Blank
characters: str # or Blank
synopsis: str
notes: str # or Blank


class Issue(BaseModel):
class BasicIssue(BaseModel):
api_url: HttpUrl
series_name: str
descriptor: str
publication_date: str
price: str
page_count: str
variant_of: Optional[HttpUrl]
series: HttpUrl

@property
def id(self) -> Optional[int]:
match = re.search(r"/issue/(\d+)/", str(self.api_url))
if match:
return int(match.group(1))
return None


class Issue(BasicIssue):
editing: str
indicia_publisher: str
brand: str
isbn: str # or Blank
barcode: str # or Blank
rating: str # or Blank
on_sale_date: str # or Blank
on_sale_date: Optional[date]
indicia_frequency: str
notes: str
variant_of: Optional[bytes] # TODO: Work out typing
series: HttpUrl
story_set: list[Story]
cover: HttpUrl

@property
def id(self) -> Optional[int]:
match = re.search(r"/issue/(\d+)/", self.api_url)
if match:
return int(match.group(1)) # Convert the extracted id to an integer
return None
10 changes: 5 additions & 5 deletions grayven/schemas/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class Publisher(BaseModel):
country: str
modified: datetime
name: str
year_began: int
year_ended: int
year_began: Optional[int]
year_ended: Optional[int]
year_began_uncertain: bool
year_ended_uncertain: bool
year_overall_began: Optional[bytes] # TODO: Work out typing
year_overall_ended: Optional[bytes] # TODO: Work out typing
year_overall_began: Optional[int]
year_overall_ended: Optional[int]
year_overall_began_uncertain: bool
year_overall_ended_uncertain: bool
notes: str # or Blank
Expand All @@ -31,7 +31,7 @@ class Publisher(BaseModel):

@property
def id(self) -> Optional[int]:
match = re.search(r"/publisher/(\d+)/", self.api_url)
match = re.search(r"/publisher/(\d+)/", str(self.api_url))
if match:
return int(match.group(1)) # Convert the extracted id to an integer
return None
6 changes: 3 additions & 3 deletions grayven/schemas/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ class Series(BaseModel):
publishing_format: str
notes: str # or Blank
year_began: int
year_ended: int
year_ended: Optional[int]
publisher: HttpUrl

@property
def id(self) -> Optional[int]:
match = re.search(r"/series/(\d+)/", self.api_url)
match = re.search(r"/series/(\d+)/", str(self.api_url))
if match:
return int(match.group(1)) # Convert the extracted id to an integer
return int(match.group(1))
return None
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ dependencies = [
"eval-type-backport >= 0.2.0; python_version < '3.10'",
"httpx[http2]>=0.28.1",
"pydantic >= 2.10.3",
"ratelimit >= 2.2.1"
"pytest-cov>=6.0.0",
"pytest>=8.3.4",
"ratelimit >= 2.2.1",
"rich>=13.9.4",
]
description = "A Python wrapper for the Grand Comics Database API."
dynamic = ["version"]
Expand Down Expand Up @@ -96,6 +99,7 @@ ignore = [
"D",
"EM101",
"FBT",
"PLR2004",
"TCH",
"TRY003"
]
Expand Down
Binary file modified tests/cache.sqlite
Binary file not shown.
61 changes: 20 additions & 41 deletions tests/issue_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""The Issue test module.
This module contains tests for Issue objects.
This module contains tests for Issue and BasicIssue objects.
"""

from datetime import date
Expand All @@ -18,7 +18,7 @@ def test_issue(session: GrandComicsDatabase) -> None:
assert result is not None
assert result.id == 242700

assert result.api_url == "https://www.comics.org/api/issue/242700/?format=json"
assert str(result.api_url) == "https://www.comics.org/api/issue/242700/?format=json"
assert result.series_name == "Green Lantern (2005 series)"
assert result.descriptor == "1 [Direct Sales - Carlos Pacheco / Jesus Merino Cover]"
assert result.publication_date == "July 2005"
Expand All @@ -36,7 +36,7 @@ def test_issue(session: GrandComicsDatabase) -> None:
assert result.on_sale_date == date(2005, 5, 25)
assert result.indicia_frequency == "monthly"
assert result.variant_of is None
assert result.series == "https://www.comics.org/api/series/13519/?format=json"
assert str(result.series) == "https://www.comics.org/api/series/13519/?format=json"
assert len(result.story_set) == 4
assert result.story_set[0].type == StoryType.COVER
assert result.story_set[0].title == ""
Expand All @@ -54,7 +54,9 @@ def test_issue(session: GrandComicsDatabase) -> None:
assert result.story_set[0].characters == "Green Lantern [Hal Jordan]"
assert result.story_set[0].synopsis == ""
assert result.story_set[0].notes == ""
assert result.cover == "https://files1.comics.org//img/gcd/covers_by_id/224/w400/224124.jpg"
assert (
str(result.cover) == "https://files1.comics.org//img/gcd/covers_by_id/224/w400/224124.jpg"
)


def test_issue_fail(session: GrandComicsDatabase) -> None:
Expand All @@ -65,51 +67,28 @@ def test_issue_fail(session: GrandComicsDatabase) -> None:

def test_list_issues(session: GrandComicsDatabase) -> None:
"""Test using the list_issues endpoint with a valid search."""
results = session.list_issues()
assert len(results) != 0
result = next(x for x in results if x.id == 242700)
results = session.list_issues(series_name="Green Lantern", issue_number=1, year=2005)
assert len(results) == 6
result = next(iter(x for x in results if x.id == 242700), None)
assert result is not None

assert result.api_url == "https://www.comics.org/api/issue/242700/?format=json"
assert str(result.api_url) == "https://www.comics.org/api/issue/242700/?format=json"
assert result.series_name == "Green Lantern (2005 series)"
assert result.descriptor == "1 [Direct Sales - Carlos Pacheco / Jesus Merino Cover]"
assert result.publication_date == "July 2005"
assert result.price == "3.50 USD; 4.75 CAD"
assert result.page_count == "48.000"
assert (
result.editing
== "Peter J. Tomasi (credited as Peter Tomasi) (editor); Harvey Richards (credited) (assistant editor); Dan DiDio (credited) (executive editor); Paul Levitz (credited) (publisher)" # noqa: E501
)
assert result.indicia_publisher == "DC Comics"
assert result.brand == "DC [bullet]"
assert result.isbn == ""
assert result.barcode == "76194124438900111"
assert result.rating == "Approved by the Comics Code Authority"
assert result.on_sale_date == date(2005, 5, 25)
assert result.indicia_frequency == "monthly"
assert result.variant_of is None
assert result.series == "https://www.comics.org/api/series/13519/?format=json"
assert len(result.story_set) == 4
assert result.story_set[0].type == StoryType.COVER
assert result.story_set[0].title == ""
assert result.story_set[0].feature == "Green Lantern"
assert result.story_set[0].sequence_number == 0
assert result.story_set[0].page_count == "1.000"
assert result.story_set[0].script == ""
assert result.story_set[0].pencils == "Carlos Pacheco (credited) (signed as Pacheco [scratch])"
assert result.story_set[0].inks == "Jesús Merino (credited) (signed as Merino)"
assert result.story_set[0].colors == "Peter Steigerwald (credited) (signed as Peter S:)"
assert result.story_set[0].letters == ""
assert result.story_set[0].editing == ""
assert result.story_set[0].job_number == ""
assert result.story_set[0].genre == "superhero"
assert result.story_set[0].characters == "Green Lantern [Hal Jordan]"
assert result.story_set[0].synopsis == ""
assert result.story_set[0].notes == ""
assert result.cover == "https://files1.comics.org//img/gcd/covers_by_id/224/w400/224124.jpg"
assert str(result.series) == "https://www.comics.org/api/series/13519/?format=json"


def test_list_issue_invalid_series(session: GrandComicsDatabase) -> None:
"""Test using the list_issues endpoint with an invalid series_name."""
results = session.list_issues(series_name="invalid", issue_number=1)
assert len(results) == 0


def test_list_issue_empty(session: GrandComicsDatabase) -> None:
"""Test using the list_issues endpoint with an invalid search."""
results = session.list_issues()
def test_list_issue_invalid_number(session: GrandComicsDatabase) -> None:
"""Test using the list_issues endpoint with an invalid issue_number."""
results = session.list_issues(series_name="Green Lantern", issue_number=-1)
assert len(results) == 0
38 changes: 6 additions & 32 deletions tests/publisher_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def test_publisher(session: GrandComicsDatabase) -> None:
assert result is not None
assert result.id == 54

assert result.api_url == "https://www.comics.org/api/publisher/54/?format=json"
assert str(result.api_url) == "https://www.comics.org/api/publisher/54/?format=json"
assert result.country == "us"
assert result.modified == datetime(2024, 12, 15, 21, 21, 8) # noqa: DTZ001
assert result.modified == datetime(2024, 12, 16, 17, 1, 37) # noqa: DTZ001
assert result.name == "DC"
assert result.year_began == 1935
assert result.year_ended is None
Expand All @@ -29,11 +29,11 @@ def test_publisher(session: GrandComicsDatabase) -> None:
assert result.year_overall_ended is None
assert result.year_overall_began_uncertain is False
assert result.year_overall_ended_uncertain is False
assert result.url == "http://www.dccomics.com/"
assert str(result.url) == "http://www.dccomics.com/"
assert result.brand_count == 28
assert result.indicia_publisher_count == 53
assert result.series_count == 9624
assert result.issue_count == 56100
assert result.series_count == 9628
assert result.issue_count == 56104


def test_publisher_fail(session: GrandComicsDatabase) -> None:
Expand All @@ -45,30 +45,4 @@ def test_publisher_fail(session: GrandComicsDatabase) -> None:
def test_list_publishers(session: GrandComicsDatabase) -> None:
"""Test using the list_publishers endpoint with a valid search."""
results = session.list_publishers()
assert len(results) != 0
result = next(x for x in results if x.id == 54)
assert result is not None

assert result.api_url == "https://www.comics.org/api/publisher/54/?format=json"
assert result.country == "us"
assert result.modified == datetime(2024, 12, 15, 21, 21, 8) # noqa: DTZ001
assert result.name == "DC"
assert result.year_began == 1935
assert result.year_ended is None
assert result.year_began_uncertain is False
assert result.year_ended_uncertain is False
assert result.year_overall_began is None
assert result.year_overall_ended is None
assert result.year_overall_began_uncertain is False
assert result.year_overall_ended_uncertain is False
assert result.url == "http://www.dccomics.com/"
assert result.brand_count == 28
assert result.indicia_publisher_count == 53
assert result.series_count == 9624
assert result.issue_count == 56100


def test_list_publishers_empty(session: GrandComicsDatabase) -> None:
"""Test using the list_publishers endpoint with an invalid search."""
results = session.list_publishers()
assert len(results) == 0
assert len(results) >= 500
Loading

0 comments on commit 510a490

Please sign in to comment.