ranobelib-python-sdk 0.6.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/PKG-INFO +1 -1
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/pyproject.toml +1 -1
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/pyproject.toml.orig +1 -1
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/__init__.py +2 -1
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/catalog.py +37 -5
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/client.py +18 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/README.md +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/cache.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/console.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exceptions.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/__init__.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/_illustrations.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/_shared.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/epub.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/fb2.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/pdf.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/txt.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/models.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/numbering.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/py.typed +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/sdk.py +0 -0
- {ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/sizing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ranobelib-python-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf
|
|
5
5
|
Keywords: ranobelib,ranobe,novel,webnovel,epub,fb2,sdk
|
|
6
6
|
Author: LLIJIYAPNIK
|
|
@@ -13,7 +13,7 @@ from ranobelib.exceptions import (
|
|
|
13
13
|
TitleNotFoundError,
|
|
14
14
|
VolumeNotFoundError,
|
|
15
15
|
)
|
|
16
|
-
from ranobelib.models import CatalogPage, Chapter, ChapterBranch, Title, Volume
|
|
16
|
+
from ranobelib.models import CatalogPage, Chapter, ChapterBranch, Genre, Title, Volume
|
|
17
17
|
from ranobelib.sdk import RanobeLib
|
|
18
18
|
from ranobelib.sizing import chapter_size, volume_size
|
|
19
19
|
|
|
@@ -26,6 +26,7 @@ __all__ = [
|
|
|
26
26
|
"ChapterBranch",
|
|
27
27
|
"ChapterNotFoundError",
|
|
28
28
|
"DownloadTitleInterruptedError",
|
|
29
|
+
"Genre",
|
|
29
30
|
"MultipleTitleTranslationsError",
|
|
30
31
|
"MultipleTranslationsError",
|
|
31
32
|
"RanobeLib",
|
|
@@ -12,8 +12,8 @@ from types import TracebackType
|
|
|
12
12
|
from typing import Any, Self
|
|
13
13
|
|
|
14
14
|
from ranobelib.cache import DEFAULT_CACHE_DIR, DiskCache
|
|
15
|
-
from ranobelib.client import ApiClient
|
|
16
|
-
from ranobelib.models import CatalogPage, Title
|
|
15
|
+
from ranobelib.client import RANOBELIB_SITE_ID, ApiClient
|
|
16
|
+
from ranobelib.models import CatalogPage, Genre, Title
|
|
17
17
|
|
|
18
18
|
DEFAULT_SORT = "last_chapter_at"
|
|
19
19
|
"""Default sort order: title's last-chapter timestamp — the closest real equivalent to
|
|
@@ -94,9 +94,8 @@ class Catalog:
|
|
|
94
94
|
query: Free-text search term, matched against name/rus_name/eng_name. ``None``
|
|
95
95
|
(the default) or an empty string lists without searching.
|
|
96
96
|
genres: Genre ids to filter by. A title must have *all* of them, not just one
|
|
97
|
-
(AND, not OR — see docs/api-notes.md).
|
|
98
|
-
|
|
99
|
-
docs/api-notes.md) — an unrecognized id just matches nothing.
|
|
97
|
+
(AND, not OR — see docs/api-notes.md). Not validated against ``list_genres()``
|
|
98
|
+
before sending — an unrecognized id just matches nothing rather than erroring.
|
|
100
99
|
status: A single ``Title.status.id`` to filter by (e.g. ongoing vs. completed).
|
|
101
100
|
sort: Sort order. Despite the name, this is sent as the API's ``sort_by``
|
|
102
101
|
parameter — an actual ``sort`` parameter exists but is silently ignored by
|
|
@@ -136,6 +135,34 @@ class Catalog:
|
|
|
136
135
|
self._cache.set(key, data)
|
|
137
136
|
return _build_page(data)
|
|
138
137
|
|
|
138
|
+
async def list_genres(self, *, refresh: bool = False) -> list[Genre]:
|
|
139
|
+
"""Fetch the full list of catalog genres (id → name), for use as filter options
|
|
140
|
+
with ``list_titles(genres=[...])``.
|
|
141
|
+
|
|
142
|
+
The underlying endpoint (``GET /api/constants?fields[]=genres``) has no site-scoping
|
|
143
|
+
parameter — it returns the full genre list shared across the whole lib.social
|
|
144
|
+
network in one shot, each tagged with the site ids it applies to (see
|
|
145
|
+
docs/api-notes.md). This filters that down to genres tagged for ranobelib.me before
|
|
146
|
+
returning, so callers don't get filter options (e.g. "Детское") that could never
|
|
147
|
+
match any ranobelib title.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
refresh: Bypass the disk cache and re-fetch from the API even if the genre list
|
|
151
|
+
was already cached.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Every ``Genre`` (id, name, adult flag) that applies to ranobelib.me.
|
|
155
|
+
"""
|
|
156
|
+
key = "catalog:genres"
|
|
157
|
+
if not refresh:
|
|
158
|
+
cached = self._cache.get(key)
|
|
159
|
+
if cached is not None:
|
|
160
|
+
return _build_genres(cached)
|
|
161
|
+
|
|
162
|
+
data = await self._client.list_genres()
|
|
163
|
+
self._cache.set(key, data)
|
|
164
|
+
return _build_genres(data)
|
|
165
|
+
|
|
139
166
|
|
|
140
167
|
def _cache_key(
|
|
141
168
|
*,
|
|
@@ -154,3 +181,8 @@ def _build_page(data: dict[str, Any]) -> CatalogPage:
|
|
|
154
181
|
items = [Title.model_validate(item) for item in data["data"]]
|
|
155
182
|
meta = data["meta"]
|
|
156
183
|
return CatalogPage(items=items, page=meta["current_page"], has_next_page=meta["has_next_page"])
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _build_genres(data: list[dict[str, Any]]) -> list[Genre]:
|
|
187
|
+
site_id = int(RANOBELIB_SITE_ID)
|
|
188
|
+
return [Genre.model_validate(item) for item in data if site_id in item.get("site_ids", [])]
|
|
@@ -217,6 +217,24 @@ class ApiClient:
|
|
|
217
217
|
result: dict[str, Any] = response.json()
|
|
218
218
|
return result
|
|
219
219
|
|
|
220
|
+
async def list_genres(self) -> list[dict[str, Any]]:
|
|
221
|
+
"""Fetch the raw genre list shared across the whole lib.social network.
|
|
222
|
+
|
|
223
|
+
Unlike ``list_titles``, this endpoint isn't site-scoped by any request parameter
|
|
224
|
+
(confirmed: a ``Site-Id`` header changes nothing about the response) — it returns
|
|
225
|
+
every genre known to the network at once, each tagged with the site ids it applies
|
|
226
|
+
to via a ``site_ids`` field. Callers must filter to ``RANOBELIB_SITE_ID`` themselves
|
|
227
|
+
(see ``Catalog.list_genres``, which drops e.g. site-5-only genres like "Детское"
|
|
228
|
+
before returning).
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
The raw ``data.genres`` array from the API response, unfiltered by site.
|
|
232
|
+
"""
|
|
233
|
+
response = await self._get("/constants", params=httpx.QueryParams([("fields[]", "genres")]))
|
|
234
|
+
self._raise_for_status(response, not_found=RanobeLibError("Unexpected 404 from genre list"))
|
|
235
|
+
data: list[dict[str, Any]] = response.json()["data"]["genres"]
|
|
236
|
+
return data
|
|
237
|
+
|
|
220
238
|
async def _get(self, url: str, *, params: Any = None) -> httpx.Response:
|
|
221
239
|
"""Issue a GET request, bounded by concurrency/pacing and retried on 429/5xx."""
|
|
222
240
|
attempt = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/__init__.py
RENAMED
|
File without changes
|
{ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/_illustrations.py
RENAMED
|
File without changes
|
{ranobelib_python_sdk-0.6.1 → ranobelib_python_sdk-0.7.0}/src/ranobelib/exporters/_shared.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|