Skip to main content

thaumiel

thaumiel mascot (SCP-3000, Anantashesha) generated by Google's Nano Banana 2

PyPI - Python Version PyPI - License PyPI - Status PyPI - Downloads

A typed, ergonomic, read-only async Python client for the SCP Foundation Wiki's Crom GraphQL API.

thaumiel wraps Crom's GraphQL endpoint in a small, fully-typed surface: fetch pages, filter and sort them with a Python DSL, page through results without touching cursors, and budget your rate-limit quota — all async, all type-checked.

Features

  • Fully typed: Frozen Pydantic v2 models (Page, Author, Attribution, ...), checked under pyright strict.
  • Ergonomic filter DSL: Build server-side filters with Python operators: (F.rating >= 100) & (F.tag == "scp"). Illegal filters raise at build time, not at the server.
  • Automatic pagination: pages() is an async iterator that follows Crom's cursors for you; fetch_page_batch() exposes them when you want manual control.
  • Costly-field provenance: Opt into expensive fields per call, and tell "not requested" apart from "server returned null" via page.requested(...).
  • Quota estimation: estimate_* predicts a call's point cost before you spend it.
  • Typed errors and optional retry: A ThaumielError hierarchy plus a configurable RetryPolicy with exponential backoff.

Installation

Requires Python 3.14+.

pip install thaumiel

Quickstart

import asyncio

from thaumiel import AsyncClient


async def main() -> None:
    async with AsyncClient() as client:
        # Crom stores SCP wiki URLs with the http:// scheme.
        page = await client.page("http://scp-wiki.wikidot.com/scp-173")
        if page is None:
            return

        print(page.title, page.rating)
        print(page.tags[:3])


asyncio.run(main())
SCP-173 10752.0
('autonomous', 'ectoentropic', 'euclid')

page() returns None (not an exception) when nothing matches, and takes either a url or a wikidot_id.

Filtering, sorting, and listing

pages() streams every match, following pagination automatically. Combine F accessors into a filter and pass a Sort:

import asyncio

from thaumiel import AsyncClient, F, Sort, SortKey


async def main() -> None:
    # Highest-rated SCP articles on the English wiki.
    query = F.url.starts_with("http://scp-wiki.wikidot.com") & (F.tag == "scp")
    async with AsyncClient() as client:
        shown = 0
        async for page in client.pages(
            filter=query, sort=Sort.by(SortKey.RATING), page_size=5
        ):
            print(f"{page.rating:>6.0f}  {page.title}")
            shown += 1
            if shown == 5:
                break


asyncio.run(main())
 10752  SCP-173
  7145  ●●|●●●●●|●●|●
  5544  SCP-049
  5240  SCP-____-J
  4790  SCP-096

Count matches without fetching them:

await client.count_pages(F.tag == "scp")   # -> 69916

Need the cursor yourself (checkpointing, UI paging)? fetch_page_batch() returns one PageBatch with .pages, .end_cursor, and .has_next_page.

The filter DSL

Each F accessor exposes only the operators its field supports; an unsupported operator or a wrong-typed value raises InvalidPredicateError immediately.

Accessor Field type Operators
F.url prefix string == != .starts_with()
F.title string (case-insensitive) == != .eq_lower() .neq_lower() .starts_with() .starts_with_lower()
F.author string (case-insensitive) same as F.title; matches an attribution's display name
F.category string == !=
F.rating int == != < <= > >=
F.created_at datetime == != < <= > >=
F.is_hidden, F.is_user_page bool == !=
F.tag tag set == (has) != (lacks) .all_of() .any_of() .none_of()

Combine predicates with & (and), | (or), and ~ (not).

A predicate lowers to Crom's GraphQL input only when a request is issued, but you can inspect it:

(F.rating >= 100).compile().model_dump(by_alias=True, exclude_unset=True)
# {'onWikidotPage': {'rating': {'gte': 100}}}

Costly fields and quota

Some fields cost extra rate-limit points and are opt-in per call. A field you don't request stays None; some can be None even when requested, so page.requested(...) disambiguates.

from thaumiel import CostlyField

page = await client.page(
    "http://scp-wiki.wikidot.com/scp-173",
    source=True,
    attributions=True,
)

print(len(page.source))                       # 1680
print(page.summary)                           # None
print(page.requested(CostlyField.SUMMARY))    # False  — we never asked for it
credit = page.attributions[0]
print(credit.type.value, credit.user_display_name)   # AUTHOR Moto42

Crom meters usage in points (reported via the x-ratelimit-remaining header; the ceiling is 300000). Estimate before you spend — costly fields in pages() are billed per page:

from thaumiel import estimate_count, estimate_page, estimate_pages

estimate_page(source=True, attributions=True)   # 4
estimate_count()                                # 2
estimate_pages(page_size=100, source=True)      # 200

Errors and retries

Every error subclasses ThaumielError:

from thaumiel import GraphQLError, RateLimitError, TransportError

try:
    page = await client.page(url)
except RateLimitError as exc:      # HTTP 429 (a subclass of TransportError)
    ...
except TransportError as exc:      # other HTTP/network failure; .status_code, .cause
    ...
except GraphQLError as exc:        # query-level errors; .errors
    ...

Every call is read-only and idempotent, so retrying is safe. RetryPolicy backs off exponentially on rate limits (and optionally on 5xx):

from thaumiel import AsyncClient, RetryPolicy

policy = RetryPolicy(max_attempts=4, backoff=0.5)
async with AsyncClient() as client:
    # Pass a factory, not a coroutine: a retry needs a fresh awaitable.
    page = await policy.run(lambda: client.page("http://scp-wiki.wikidot.com/scp-173"))

Configuration

from thaumiel import AsyncClient

client = AsyncClient(
    user_agent="my-app/1.0 (me@example.com)",   # good Crom etiquette
    timeout=30.0,
)

For full control — connection limits, event hooks, observing quota headers — inject your own httpx.AsyncClient. thaumiel will not close a client it did not create:

import httpx
from thaumiel import AsyncClient

http = httpx.AsyncClient(headers={"User-Agent": "my-app/1.0"})
client = AsyncClient(http_client=http)
# ... use client ...
await http.aclose()   # you own it; you close it

More end-to-end scripts live in examples/.

Limitations

  • Read-only: thaumiel offers no writes.
  • Async only: There is no synchronous client.
  • Wikidot pages only: pages() skips non-Wikidot nodes (e.g. RuFoundation), so it can yield fewer rows than count_pages reports for the same filter.
  • Curated filter surface: Only the fields in the table above are filterable, and some support equality only.
  • Quota-bound: Requests cost points against Crom's quota; budget with estimate_*.
  • Alpha: While on 0.x, the public API may change before 1.0.

Development

See .github/CONTRIBUTING.md.

License

MIT — see LICENSE.

Metadata

Release files for thaumiel 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for thaumiel 0.1.0
File Size Uploaded Provenance Actions
thaumiel-0.1.0.tar.gz 42.6 kB With provenance Details for thaumiel-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for thaumiel 0.1.0
File Interpreter ABI Platform Actions
thaumiel-0.1.0-py3-none-any.whl Python 3 none any Details for thaumiel-0.1.0-py3-none-any.whl

Total release size: 73.7 kB

Release files / thaumiel-0.1.0.tar.gz

Download URL thaumiel-0.1.0.tar.gz
Size 42.6 kB
Tags Source
SHA-256 checksum
How to use checksums
10dfb9de96b67fda66181d74d2829118419ac4d2e072af70e6eea0a4a0d4f8e8
BLAKE2b-256 checksum
How to use checksums
847c3ca7cb71b31cd0f73b33f4e9e45b989ce497a9da72054c38381071039a5f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Release files / thaumiel-0.1.0-py3-none-any.whl

Download URL thaumiel-0.1.0-py3-none-any.whl
Size 31.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dc25173acffd3ee319793232759c16c42816a4dad603760424141803936bcc17
BLAKE2b-256 checksum
How to use checksums
25f1baea31c6fbe651196a85f226c660ab0559d1855ff7b8ba965929a69a3920
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Security for thaumiel 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for thaumiel-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page