Skip to main content

discourse-reader

A typed, read-only Python client for the Discourse forum API.

Install

pip install discourse-reader

Quick start

from discourse_reader import DiscourseClient

client = DiscourseClient("https://meta.discourse.org")

# Browse categories
for cat in client.categories():
    print(f"{cat.name}: {cat.topic_count} topics")

# Get a topic with all its posts
topic = client.topics.get(12345)
print(topic.title)
print(topic.opening_post.cooked)       # the original post (HTML)
print(topic.accepted_answer)           # accepted answer or None
for reply in topic.posts.replies():
    print(reply.username, reply.cooked)

API

Site-level (flat on client)

client.about()                         # About
client.statistics()                    # SiteStatistics
client.categories()                    # list[Category]
client.tags()                          # list[TagDetail]
client.user("username")                # User
client.search("query", limit=50)       # Iterator[SearchPost]

Topics (client.topics)

client.topics.latest(limit=100)        # Iterator[Topic]
client.topics.top(period="monthly")    # Iterator[Topic]
client.topics.by_category(cat)         # Iterator[Topic]  (pass a Category)
client.topics.by_tag("tag-name")       # Iterator[Topic]
client.topics.get(topic_id)            # TopicResult

All listing methods are lazy iterators with optional limit.

TopicResult

topics.get() returns a TopicResult which delegates to TopicDetail for attributes like title, category_id, views, etc.

topic = client.topics.get(12345)
topic.title                            # str (delegated to TopicDetail)
topic.opening_post                     # Post  -- the original post
topic.accepted_answer                  # Post | None
topic.detail                           # raw TopicDetail model

Posts (topic.posts)

Discourse delivers ~20 posts with the topic detail. The rest are fetched lazily in batches when you iterate.

topic.posts.all()                      # Iterator[Post] -- everything
topic.posts.replies()                  # Iterator[Post] -- everything except OP
len(topic.posts)                       # total post count
for post in topic.posts:               # same as .all()
    ...

Single post

client.posts.get(post_id)             # Post by global ID

Extra fields

All models use extra="allow" -- core fields are typed, plugin fields land in model_extra:

topic.detail.model_extra.get("accepted_answer")   # solved plugin data
post.model_extra.get("accepted_answer")            # per-post flag

Rate limiting

Default: 4 requests/second. Configurable via constructor. Automatic 429 retry with Retry-After.

client = DiscourseClient("https://...", requests_per_second=2)    # slower
client = DiscourseClient("https://...", requests_per_second=None)  # no limit

Development

uv sync
uv run pre-commit install
uv run pre-commit run --all-files
uv run pytest

Release files for discourse-reader 1.0.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 discourse-reader 1.0.0
File Size Uploaded
discourse_reader-1.0.0.tar.gz 38.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for discourse-reader 1.0.0
File Interpreter ABI Platform
discourse_reader-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 47.1 kB

Release files / discourse_reader-1.0.0.tar.gz

Download URL discourse_reader-1.0.0.tar.gz
Size 38.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5e5893c0f7495217ced61f49d608327823af3343222b0b1d8fa329fe3359a3e6
BLAKE2b-256 checksum
How to use checksums
c80b460fd52d8482468e684a1b617f1bf1c71f7df25b13a49e1ee3d828d77c38
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 Apr 16, 2026.

Transparency log

Release files / discourse_reader-1.0.0-py3-none-any.whl

Download URL discourse_reader-1.0.0-py3-none-any.whl
Size 8.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
54064a139afca9a255cccda55540934a2f10d73658988b99fa676e25e82f890b
BLAKE2b-256 checksum
How to use checksums
00ccab1f423b8a93a8426f509528a61d1a70d2e224467536c5f254ccbb1686a8
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 Apr 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

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