audora-wattpad-py (0.1.0)
Installation
pip install --index-url audora-wattpad-pyAbout this package
Async Wattpad API client implementing the audora-provider-base contract
audora-wattpad-py
An async Wattpad API client for Python 3.12+, implementing the BaseProvider contract from
audora-provider-base.
Because it implements a shared contract, code written against it ports to any other provider in the family with no changes beyond the constructor.
Unofficial. Wattpad publishes no public API; this is built against community reverse-engineering documented at
Archive-WP/WattpadAPIDocumentation. Endpoints can change without notice.
Install
audora-provider-base lives in a Forgejo registry rather than on PyPI, so the index has to
be declared.
# uv reads [[tool.uv.index]] from pyproject.toml
uv add audora-wattpad-py
# pip needs it on the command line
pip install --extra-index-url https://forge.towu.dev/api/packages/aaron/pypi/simple \
audora-wattpad-py
Usage
import asyncio
from audora_wattpad import WattpadProvider
async def main() -> None:
async with WattpadProvider() as wp:
story = await wp.get_story("336970902")
print(story.title, "by", story.author)
# Chapter stubs arrive with get_story; load_chapters fills in the text
# in a single request via Wattpad's bulk endpoint.
await wp.load_chapters(story, with_text=True)
for chapter in story.chapters:
print(chapter.title, len(chapter.text), "chars")
asyncio.run(main())
Authenticated use:
async with WattpadProvider() as wp:
await wp.authenticate("username", "password")
library = await wp.get_library()
async for story in wp.get_collection_stories(library):
print(story.title)
# Sessions are exportable, so you log in once rather than once per process.
session = wp.export_session() # contains secrets; store it like a password
# ... later, in another process
async with WattpadProvider() as wp:
wp.restore_session(session) # 0 requests
Notes specific to Wattpad
- Rate limiting. Wattpad publishes no limits. This client defaults to a conservative
2 requests/second; pass
limiter=to override.from audora_provider_base import TokenBucketLimiter WattpadProvider(limiter=TokenBucketLimiter(rate=5.0)) - Caching. No cache is configured by default. If you pass
cache=, do not share one backend between authenticated and anonymous clients, or between two accounts — Wattpad returns per-account data (voted,following, drafts) from the same URLs. - Followers cap at 2000. Wattpad refuses to page past 2000 followers. The paginator treats that as clean end-of-data rather than raising.
UNSETvsNone. A field that isUNSETwas not part of the projection you requested;Nonemeans Wattpad says there is no value. Useaudora_provider_base.is_set()when the difference matters.
Development
uv sync --extra dev
rtk ruff format . && rtk ruff check --fix . && rtk mypy src/ tests/ && rtk pytest
Testing
Offline tests need no network and no credentials:
rtk pytest -m "not live"
Live tests run against a real account and are skipped when unconfigured. Use a throwaway account — the suite writes to it.
| Variable | Purpose |
|---|---|
WATTPAD_USERNAME, WATTPAD_PASSWORD |
Account credentials. Absent → all live tests skip. |
WATTPAD_TOKEN |
Use an existing session token; wins over username/password and skips the login request. |
WATTPAD_STORY_ID |
story_ref for the contract suite. Must be owned by the account and have at least one published chapter — the inline-image tests write into its first chapter. |
WATTPAD_MISSING_STORY_ID |
A story that does not exist. Default 1. |
WATTPAD_USER |
user_ref. Defaults to the logged-in username. |
WATTPAD_LIST_ID |
A small (2–3 story) reading list owned by the account. |
WATTPAD_PART_ID |
A chapter to exercise. Defaults to the first part of WATTPAD_STORY_ID. |
WATTPAD_SEARCH_QUERY |
Query returning more than one page. Default the. |
WATTPAD_FOLLOW_TARGET |
A username to follow and unfollow. |
WATTPAD_ALLOW_WRITES |
Set to 1 to enable every mutating test. |
WATTPAD_USERNAME=... WATTPAD_PASSWORD=... WATTPAD_STORY_ID=... rtk pytest -m live
WATTPAD_ALLOW_WRITES=1 WATTPAD_USERNAME=... ... rtk pytest
rtk pytest -rs # audit what skipped, and why
Account setup the contract suite expects. Verified against a real account; each of these is a test that fails, rather than skips, if it is not met.
- Email-verified. An unverified account is refused with error 1073 on gated actions,
followamong them. - Owns
WATTPAD_STORY_ID, which needs at least one published chapter. The image tests write into the first one, so read access is not enough. - Owns
WATTPAD_LIST_ID, a small reading list. - A library spanning more than one page — more than 20 entries, so roughly 25. Below
that the whole library fits in one page, no fan-out is ever required, and
test_load_stories_refuses_fanoutfails with nothing to refuse.
One caveat on reruns: newstory is idempotent on the title, so a test that creates a story
with a fixed name will keep resolving the first one, even after it is deleted. The suite
does not do this, but a scratch script easily might.
Licence
MIT.