Skip to content

Commit

Permalink
feat: search() is now a generator (#8)
Browse files Browse the repository at this point in the history
* feat: search() is now a generator

* fix: tests

* fix: tests

* fix: tests

* fix remove 3.13

* docs: update

* docs: fix broken code block

* fix: docstrings
  • Loading branch information
Ravencentric authored Sep 16, 2024
1 parent cf2d7c5 commit b41e559
Show file tree
Hide file tree
Showing 48 changed files with 730 additions and 1,041 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- uses: actions/cache@v4
name: Define a cache for the venv based on the dependencies lock file
with:
key: ubuntu-latest-3.12-${{ hashFiles('poetry.lock') }}
key: 3.12-${{ hashFiles('poetry.lock') }}
path: ./.venv

- name: Install dependencies
Expand Down
13 changes: 6 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ on:
paths:
- 'src/**'
- 'tests/**'
- '.github\workflows\test.yml'
- '.github/workflows/test.yml'
pull_request:
branches:
- main
paths:
- 'src/**'
- 'tests/**'
- '.github\workflows\test.yml'
- '.github/workflows/test.yml'
workflow_dispatch:

defaults:
Expand All @@ -27,9 +27,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
python-version: ['3.10', '3.11', '3.12']
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -53,7 +52,7 @@ jobs:
- uses: actions/cache@v4
name: Define a cache for the venv based on the dependencies lock file
with:
key: ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
key: ${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
path: ./.venv

- name: Install the project dependencies
Expand All @@ -73,7 +72,7 @@ jobs:

- uses: actions/upload-artifact@v4
with:
name: pynyaa-${{ steps.version.outputs.version }}-${{ matrix.python-version }}-${{ matrix.os }}
name: pynyaa-${{ steps.version.outputs.version }}-${{ matrix.python-version }}
path: "dist/*"

- name: Upload coverage to Codecov
Expand Down
4 changes: 0 additions & 4 deletions docs/api-reference/exceptions.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/api-reference/types.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
::: pynyaa._types.SearchLimit
::: pynyaa._types.MagnetUrl
1 change: 0 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,3 @@ nav:
- Models: api-reference/models.md
- Enums: api-reference/enums.md
- Types: api-reference/types.md
- Exceptions: api-reference/exceptions.md
1,268 changes: 646 additions & 622 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Ravencentric <me@ravencentric.cc>"]
license = "Unlicense"
readme = "README.md"
keywords = ["anime", "nyaa", "nyaa.si", "torrent", "python"]
packages = [{include = "pynyaa", from = "src"}]
packages = [{ include = "pynyaa", from = "src" }]
homepage = "https://pynyaa.ravencentric.cc"
repository = "https://github.com/Ravencentric/pynyaa"
documentation = "https://pynyaa.ravencentric.cc"
Expand Down Expand Up @@ -42,14 +42,14 @@ pytest = "^8.2.2"
pytest-asyncio = "^0.23.5.post1"
pre-commit = "^3.7.1"
coverage = "^7.5.4"
respx = "^0.21.1"
types-beautifulsoup4 = "^4.12.0.20240511"
types-xmltodict = "^0.13.0.3"


[tool.poetry.group.docs.dependencies]
mkdocs-material = "^9.5.27"
mkdocstrings = {extras = ["python"], version = "^0.25.1"}
mkdocs-autorefs = "^1.0.1"
mkdocs-material = "^9.5.34"
mkdocstrings = { extras = ["python"], version = "^0.26.1" }
mkdocs-autorefs = "^1.2.0"

[tool.poetry.scripts]
pynyaa = "pynyaa.__main__:main"
Expand Down
13 changes: 6 additions & 7 deletions src/pynyaa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from httpx import HTTPStatusError
from pydantic import ValidationError

from ._clients import AsyncNyaa, Nyaa
from ._enums import NyaaCategory, NyaaFilter
from ._models import NyaaTorrentPage, Submitter
from ._version import Version, _get_version
from pynyaa._clients._async import AsyncNyaa
from pynyaa._clients._sync import Nyaa
from pynyaa._enums import NyaaCategory, NyaaFilter
from pynyaa._models import NyaaTorrentPage, Submitter
from pynyaa._version import Version, _get_version

__version__ = _get_version()
__version_tuple__ = Version(*map(int, __version__.split(".")))
__version_tuple__ = Version(*[int(i) for i in __version__.split(".")])

__all__ = (
# Clients
Expand All @@ -21,5 +21,4 @@
"Submitter",
# Exceptions
"HTTPStatusError",
"ValidationError",
)
2 changes: 1 addition & 1 deletion src/pynyaa/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def main() -> None:
"""
import argparse

from ._clients import Nyaa
from pynyaa._clients._sync import Nyaa

parser = argparse.ArgumentParser(prog="pynyaa", description="Get the JSON representation of a Nyaa torrent page.")
parser.add_argument("url", type=str, help="Nyaa URL", metavar="https://nyaa.si/view/...")
Expand Down
4 changes: 0 additions & 4 deletions src/pynyaa/_clients/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
from ._async import AsyncNyaa
from ._sync import Nyaa

__all__ = ["AsyncNyaa", "Nyaa"]
38 changes: 12 additions & 26 deletions src/pynyaa/_clients/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
from urllib.parse import urljoin

from hishel import AsyncCacheClient, AsyncFileStorage
from pydantic import validate_call
from torf import Torrent
from typing_extensions import AsyncGenerator

from .._enums import NyaaCategory, NyaaFilter
from .._models import NyaaTorrentPage
from .._parser import parse_nyaa_rss_page, parse_nyaa_torrent_page
from .._types import SearchLimit
from .._utils import get_user_cache_path
from pynyaa._enums import NyaaCategory, NyaaFilter
from pynyaa._models import NyaaTorrentPage
from pynyaa._parser import parse_nyaa_rss_page, parse_nyaa_torrent_page
from pynyaa._utils import get_user_cache_path


class AsyncNyaa:
@validate_call
def __init__(self, base_url: str = "https://nyaa.si/", cache: bool = True, **kwargs: Any) -> None:
"""
Async Nyaa client.
Expand Down Expand Up @@ -58,7 +56,6 @@ def cache_path(self) -> Path:
"""
return get_user_cache_path()

@validate_call
async def get(self, page: int | str) -> NyaaTorrentPage:
"""
Retrieve information from a Nyaa torrent page.
Expand All @@ -72,8 +69,6 @@ async def get(self, page: int | str) -> NyaaTorrentPage:
Raises
------
ValidationError
Invalid input
HTTPStatusError
Nyaa returned a non 2xx response.
Expand Down Expand Up @@ -103,15 +98,13 @@ async def get(self, page: int | str) -> NyaaTorrentPage:

return NyaaTorrentPage(id=nyaaid, url=url, torrent=torrent, **parsed) # type: ignore

@validate_call
async def search(
self,
query: str,
*,
category: NyaaCategory | None = None,
filter: NyaaFilter | None = None,
limit: SearchLimit = 3,
) -> tuple[NyaaTorrentPage, ...]:
) -> AsyncGenerator[NyaaTorrentPage]:
"""
Search for torrents on Nyaa.
Expand All @@ -123,22 +116,16 @@ async def search(
The category to filter the search. If None, searches all categories.
filter : NyaaFilter, optional
The filter to apply to the search results. If None, no filter is applied.
limit : SearchLimit, optional
Maximum number of search results to retrieve. Defaults to 3. Maximum is 75.
Be cautious with this; higher limits increase the number of requests,
which may trigger rate limiting responses (HTTP 429) or get your IP banned entirely.
Raises
------
ValidationError
Invalid input
HTTPStatusError
Nyaa returned a non 2xx response.
Returns
Yields
-------
tuple[NyaaTorrentPage, ...]
A tuple of NyaaTorrentPage objects representing the retrieved data.
NyaaTorrentPage
A NyaaTorrentPage object representing the retrieved data.
"""
async with AsyncCacheClient(storage=self._storage, **self._kwargs) as client:
params = dict(
Expand All @@ -150,8 +137,7 @@ async def search(

nyaa = await client.get(self._base_url, params=params, extensions=self._extensions) # type: ignore
nyaa.raise_for_status()
results = parse_nyaa_rss_page(nyaa.text, limit)

parsed = [await self.get(link) for link in results]
results = parse_nyaa_rss_page(nyaa.text)

return tuple(parsed)
for link in results:
yield await self.get(link)
38 changes: 12 additions & 26 deletions src/pynyaa/_clients/_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
from urllib.parse import urljoin

from hishel import CacheClient, FileStorage
from pydantic import validate_call
from torf import Torrent
from typing_extensions import Generator

from .._enums import NyaaCategory, NyaaFilter
from .._models import NyaaTorrentPage
from .._parser import parse_nyaa_rss_page, parse_nyaa_torrent_page
from .._types import SearchLimit
from .._utils import get_user_cache_path
from pynyaa._enums import NyaaCategory, NyaaFilter
from pynyaa._models import NyaaTorrentPage
from pynyaa._parser import parse_nyaa_rss_page, parse_nyaa_torrent_page
from pynyaa._utils import get_user_cache_path


class Nyaa:
@validate_call
def __init__(self, base_url: str = "https://nyaa.si/", cache: bool = True, **kwargs: Any) -> None:
"""
Nyaa client.
Expand Down Expand Up @@ -58,7 +56,6 @@ def cache_path(self) -> Path:
"""
return get_user_cache_path()

@validate_call
def get(self, page: int | str) -> NyaaTorrentPage:
"""
Retrieve information from a Nyaa torrent page.
Expand All @@ -72,8 +69,6 @@ def get(self, page: int | str) -> NyaaTorrentPage:
Raises
------
ValidationError
Invalid input
HTTPStatusError
Nyaa returned a non 2xx response.
Expand All @@ -100,15 +95,13 @@ def get(self, page: int | str) -> NyaaTorrentPage:

return NyaaTorrentPage(id=nyaaid, url=url, torrent=torrent, **parsed) # type: ignore

@validate_call
def search(
self,
query: str,
*,
category: NyaaCategory | None = None,
filter: NyaaFilter | None = None,
limit: SearchLimit = 3,
) -> tuple[NyaaTorrentPage, ...]:
) -> Generator[NyaaTorrentPage]:
"""
Search for torrents on Nyaa.
Expand All @@ -120,22 +113,16 @@ def search(
The category to filter the search. If None, searches all categories.
filter : NyaaFilter, optional
The filter to apply to the search results. If None, no filter is applied.
limit : SearchLimit, optional
Maximum number of search results to retrieve. Defaults to 3. Maximum is 75.
Be cautious with this; higher limits increase the number of requests,
which may trigger rate limiting responses (HTTP 429) or get your IP banned entirely.
Raises
------
ValidationError
Invalid input
HTTPStatusError
Nyaa returned a non 2xx response.
Returns
Yields
-------
tuple[NyaaTorrentPage, ...]
A tuple of NyaaTorrentPage objects representing the retrieved data.
NyaaTorrentPage
A NyaaTorrentPage object representing the retrieved data.
"""
with CacheClient(storage=self._storage, **self._kwargs) as client:
params = dict(
Expand All @@ -146,8 +133,7 @@ def search(
)

nyaa = client.get(self._base_url, params=params, extensions=self._extensions).raise_for_status() # type: ignore
results = parse_nyaa_rss_page(nyaa.text, limit)

parsed = [self.get(link) for link in results]
results = parse_nyaa_rss_page(nyaa.text)

return tuple(parsed)
for link in results:
yield self.get(link)
2 changes: 2 additions & 0 deletions src/pynyaa/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Submitter(ParentModel):
Submitter(name='Jane', url='https://nyaa.si/user/jane', is_trusted=False, is_banned=False),
Submitter(name='John', url='https://nyaa.si/user/john', is_trusted=True, is_banned=False)
}
```
"""

name: str
Expand Down Expand Up @@ -132,6 +133,7 @@ class NyaaTorrentPage(ParentModel):
NyaaTorrentPage(title='[SubsPlease] Hibike! Euphonium S3 - 13 (1080p) [230618C3].mkv', url='https://nyaa.si/view/1839783', category='Anime - English-translated', date='2024-06-30T10:32:46+00:00', submitter='subsplease'),
NyaaTorrentPage(title='[SubsPlease] One Piece - 1110 (1080p) [B66CAB32].mkv', url='https://nyaa.si/view/1839609', category='Anime - English-translated', date='2024-06-30T02:12:07+00:00', submitter='subsplease')
}
```
"""

id: int
Expand Down
9 changes: 2 additions & 7 deletions src/pynyaa/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def parse_nyaa_torrent_page(base_url: str, html: str) -> dict[str, Any]:
)


def parse_nyaa_rss_page(xml: str, limit: int) -> tuple[str, ...]:
def parse_nyaa_rss_page(xml: str) -> tuple[str, ...]:
"""
Parse the torrent links out of the RSS page
Expand All @@ -141,9 +141,4 @@ def parse_nyaa_rss_page(xml: str, limit: int) -> tuple[str, ...]:
if isinstance(items, dict): # RSS returns single results as a dict instead of a list
items = [items]

if limit > len(items):
parsed = [item["guid"]["#text"] for item in items]
else:
parsed = [item["guid"]["#text"] for item in items[:limit]]

return tuple(parsed)
return tuple(item["guid"]["#text"] for item in items)
5 changes: 1 addition & 4 deletions src/pynyaa/_types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from pydantic import AnyUrl, Field, UrlConstraints
from pydantic import AnyUrl, UrlConstraints
from typing_extensions import Annotated

SearchLimit = Annotated[int, Field(gt=0, le=75)]
"""Integer with lower and upper limits."""

MagnetUrl = Annotated[AnyUrl, UrlConstraints(allowed_schemes=["magnet"])]
"""Url that only allows magnets."""
Binary file not shown.
Binary file removed tests/__responses__/1422797
Binary file not shown.
Binary file removed tests/__responses__/1422797.torrent
Binary file not shown.
Binary file removed tests/__responses__/1544043
Binary file not shown.
Binary file removed tests/__responses__/1544043.torrent
Binary file not shown.
Binary file removed tests/__responses__/1586776
Binary file not shown.
Binary file removed tests/__responses__/1586776.torrent
Binary file not shown.
Binary file removed tests/__responses__/1694824
Binary file not shown.
Binary file removed tests/__responses__/1694824.torrent
Binary file not shown.
Binary file removed tests/__responses__/1755409
Binary file not shown.
Binary file removed tests/__responses__/1755409.torrent
Binary file not shown.
Binary file removed tests/__responses__/1765655
Binary file not shown.
Binary file removed tests/__responses__/1765655.torrent
Binary file not shown.
Binary file removed tests/__responses__/1837420
Binary file not shown.
Binary file removed tests/__responses__/1837420.torrent
Binary file not shown.
Binary file removed tests/__responses__/1837736
Binary file not shown.
Binary file removed tests/__responses__/1837736.torrent
Binary file not shown.
Binary file removed tests/__responses__/1838091
Binary file not shown.
Binary file removed tests/__responses__/1838091.torrent
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed tests/__responses__/5819
Binary file not shown.
Binary file removed tests/__responses__/5819.torrent
Binary file not shown.
Binary file not shown.
Binary file removed tests/__responses__/76777
Binary file not shown.
Binary file removed tests/__responses__/76777.torrent
Binary file not shown.
Binary file removed tests/__responses__/884488
Binary file not shown.
Binary file removed tests/__responses__/884488.torrent
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit b41e559

Please sign in to comment.