Skip to content

Public mode, Python 3.14, clean ty#74

Merged
lxcode merged 1 commit intomainfrom
feat/public-mode
Apr 20, 2026
Merged

Public mode, Python 3.14, clean ty#74
lxcode merged 1 commit intomainfrom
feat/public-mode

Conversation

@lxcode
Copy link
Copy Markdown
Contributor

@lxcode lxcode commented Apr 20, 2026

Summary

  • Closes Way to use solely public APIs #32 — Truthbrush can now be used against Truth Social's public endpoints without credentials, via Api(require_auth=False) or truthbrush --no-auth …. Default stays strict, so existing callers see no change.
  • Bumps Python floor to 3.14 and moves the CI publish workflow to setup-python@v5 + python-version: "3.14".
  • Drives ty check truthbrush/ from 35 pre-existing diagnostics to zero; ruff format + ruff check are clean.
  • Cherry-picks two useful pieces from the stale PR Tests and Updates #41: fixes search(resolve: bool = 4) default (was an int), and lets pull_statuses accept user_id directly so public-mode callers can skip an extra lookup.

Public mode — how it works

Api.__init__ grows a keyword-only require_auth: bool = True flag. When False and no token is set, __check_login early-returns instead of raising LoginErrorException. _get/_get_paginated build a headers dict that only attaches Authorization: Bearer … when self.auth_id is not None — no more accidental Bearer None.

CLI: module-level api = Api() is gone. The cli group takes --no-auth, constructs Api(require_auth=not no_auth), and stashes it in ctx.obj["api"]. All 13 commands pick up @click.pass_context + a ctx: click.Context parameter.

Verified end-to-end with an empty environment:

$ env -u TRUTHSOCIAL_TOKEN -u TRUTHSOCIAL_USERNAME -u TRUTHSOCIAL_PASSWORD \
    truthbrush --no-auth user realDonaldTrump
{"id": "107780257626128497", "username": "realDonaldTrump", …}
$ env -u TRUTHSOCIAL_TOKEN -u TRUTHSOCIAL_USERNAME -u TRUTHSOCIAL_PASSWORD \
    truthbrush --no-auth trends
{"errors": [{"error_code": "USER_UNAUTHENTICATED", …}], …}
$ env -u TRUTHSOCIAL_TOKEN -u TRUTHSOCIAL_USERNAME -u TRUTHSOCIAL_PASSWORD \
    truthbrush trends     # no --no-auth
truthbrush.api.LoginErrorException: Username is missing.

So the client doesn't try to decide which endpoints are public — it just passes through unauthenticated and lets Truth Social's server return whatever it returns.

Cherry-picks from PR #41

  1. search(resolve: bool = 4)resolve: bool = True. The default was an int despite the bool annotation — a latent bug.
  2. pull_statuses(username=None, *, user_id=None): either identifier works; supplying user_id skips the self.lookup(username) roundtrip. Relevant to public mode since lookup may or may not be publicly accessible. Also calls __check_login explicitly now (previously it relied on the transitive call through lookup).

Not cherry-picked: the broader test/CI restructure, the utils.py split, and the __check_login_check_login rename — all scope creep for this PR.

Tests

Two new offline tests in test/test_api.py:

  • test_public_mode_does_not_require_credentials — constructs Api(require_auth=False) with no creds and calls user_likes("abc", top_num=0), which short-circuits after __check_login returns. Old code would have raised LoginErrorException from __check_login; new code returns gracefully.
  • test_strict_mode_still_raises_without_credentials — same Api() in strict mode, lookup(…) must raise.

Both use monkeypatch.delenv as belt-and-suspenders against a loaded .env.

Public/unauthenticated mode (closes #32)
- Api gains keyword-only require_auth=True parameter; default preserves
  strict behavior so no existing caller changes.
- __check_login early-returns when require_auth=False and no token is
  present, instead of raising LoginErrorException.
- _get and _get_paginated omit the Authorization header when auth_id is
  None. Sending no header beats sending "Bearer None".
- CLI: --no-auth global flag on the group, Api constructed once via
  ctx.obj["api"]; all 13 commands take click.Context via pass_context.
  Verified end-to-end: `truthbrush --no-auth user realDonaldTrump`
  returns JSON; `truthbrush trends` (strict, no creds) still raises.

Python 3.14
- pyproject.toml: python = "^3.14"; ruff target-version = "py314".
- Workflow: actions/setup-python bumped to @v5 with python-version: 3.14
  (3.10-dev was a stale pre-release pin).
- Ruff auto-fix pass: timezone.utc -> UTC; stale quoted forward-ref on
  date_to_bound unquoted.

Type hygiene
- ty check truthbrush/ goes from 35 pre-existing diagnostics to zero:
  x: T = None defaults -> X | None = None throughout; generator return
  types -> Iterator[T]; datetime.utcnow() -> datetime.now(UTC);
  ratelimit_reset null-guarded; sorted() over JSON payload narrowed via
  cast(list[dict], result); pull_statuses result-shape validation
  reordered so a non-list response breaks the loop instead of crashing
  in sorted().

Drive-bys cherry-picked from PR #41
- search(resolve: bool = 4) -> True. Default was an int despite the
  bool annotation.
- pull_statuses(username=None, ..., *, user_id=None): either identifier
  works; supplying user_id directly skips the lookup roundtrip, which
  matters in public mode where lookup may be gated. Also calls
  __check_login explicitly (previously relied on transitive call).

Tests
- Two offline tests: one asserts Api(require_auth=False) without creds
  does not raise on a method call that short-circuits before HTTP; one
  asserts the default strict mode still raises LoginErrorException
  without creds. Both use monkeypatch.delenv to guard against a
  loaded .env.

README
- New "Public mode (no credentials)" subsection under Installation.
- --no-auth added to the help block.
- Stale "Please format your code with black" replaced with the ruff/ty
  workflow introduced in 0.3.0.

Authors
- pyproject.toml authors gains David Thiel.
@lxcode lxcode merged commit d1e2e6a into main Apr 20, 2026
@lxcode lxcode deleted the feat/public-mode branch April 20, 2026 11:29
@lxcode lxcode mentioned this pull request Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Way to use solely public APIs

1 participant