audora-wattpad-py (0.1.0)

Published 2026-07-29 07:31:43 +02:00 by aaron

Installation

pip install --index-url  audora-wattpad-py

About 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.
  • UNSET vs None. A field that is UNSET was not part of the projection you requested; None means Wattpad says there is no value. Use audora_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, follow among 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_fanout fails 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.

Requirements

Requires Python: >=3.12
Details
PyPI
2026-07-29 07:31:43 +02:00
2
MIT
175 KiB
Assets (2)
Versions (1) View all
0.1.0 2026-07-29