Skip to content

Releases: fschwar4/saia_python

v0.9.0 — native async transport

Choose a tag to compare

@fschwar4 fschwar4 released this 09 Jul 10:52

Added

  • Native async transport behind a new [async] extra
    (pip install saia-python[async], pulls httpx). New saia_python.aio
    module: AsyncSAIAClient + AsyncChatService / AsyncArcanaService /
    AsyncModelsService, the httpx.AsyncClient twins of the sync data plane —
    chat completions and ARCANA RAG chat (streaming + non-streaming),
    get_rate_limits, and health_check. Use it as an async context manager;
    from saia_python import AsyncSAIAClient resolves lazily so importing the
    package never pulls httpx for sync-only users. See ADR-0007.
    • The retry brains are shared, not copied. aexecute /
      apost_chat_completion reuse RetryPolicy / _plan / _jitter /
      resolve_retry / parse_rate_limits verbatim, so the async path honours the
      same 429 policy (ADR-0006) as the sync path and the two cannot drift. The
      retry keyword works identically (retry=False fails fast; a RetryPolicy
      tunes it).
    • AsyncSSEStream owns the httpx client.stream(...) context, retrying
      an initial 429 before the body is exposed (never mid-stream). Two
      consumption modes: async for chunk in stream (decoded dicts; raises on an
      error status) or async for line in stream.aiter_lines() (raw lines, no
      raise — lets a gateway frame upstream errors itself).
  • Informative 429 errors (format_rate_limit_error, exported). When retry is
    off or the budget is spent, RateLimitError now carries a human-readable
    message — which window was hit, when it resets, and how to auto-retry — instead
    of only the bare server string. Shared by both transports (raised from
    raise_for_status).
  • Pure request builders (saia_python._payloads, re-exported at top level):
    build_chat_body, apply_arcana_fields, arcana_chat_headers, and the
    INFERENCE_SERVICE constant. Transport-free, so the sync services, the async
    services, and external gateways (the AVOR adapter) share one definition of
    the three-part ARCANA injection invariant (enable-tools + arcana.id body
    fields + the inference-service header) — defined and unit-tested once.

Changed

  • Every SAIAError now carries status_code + response_body (previously
    only APIError did). Lets a caller reframe the exact upstream response.
    RateLimitError keeps its rate_limits attribute (unchanged signature).
  • raise_for_status is transport-agnostic — it accepts a requests.Response
    (honouring .ok) or an httpx.Response (deciding success from status_code),
    so one implementation serves both paths. No behaviour change for sync callers.
  • The test extra now installs [async] too, so CI exercises the async suite.

Notes

  • Async covers the data plane + read-only control-plane calls. File
    upload/index/sync, voice, and document conversion stay sync-only on
    SAIAClient (batch/admin work, blocking file I/O, no concurrency benefit). See
    ADR-0007 for the scope rationale.
  • Tests: +42 async tests (test_async_transport / _streaming / _arcana /
    _chat / _client, test_payloads, test_rate_limit_message); the suite
    grows 198 → 240 and stays mypy- and ruff-clean.

v0.8.0

Choose a tag to compare

@fschwar4 fschwar4 released this 28 Jun 15:35

Full Changelog: v0.7.0...v0.8.0

v0.7.0 — Tokenizers

Choose a tag to compare

@fschwar4 fschwar4 released this 22 Jun 16:46

Added — Tokenizers (opt-in [tokenizer] extra)

Download the GWDG open-weight models' tokenizers and measure prompts offline:
chat-template token counts, special/structural-token overhead (absolute + relative),
subword fertility, system-prompt-from-file, and token_distribution for sizing a RAG
corpus (incl. images) against the embedding model qwen3-embedding-4b.

  • New saia_python.tokenizer module and client.tokenizers service.
  • download_tokenizer / download_all_tokenizers (tokenizer files only, never weights),
    load_tokenizer, HF_TOKEN support, and an expressive GatedRepoAccessError.
  • count_tiktoken_tokens for the external OpenAI models.
  • Docs: Extensions guide + quickstart section; runnable examples/tokenizer_features.ipynb.

Install: pip install saia-python[tokenizer]

Full changelog: https://github.com/fschwar4/saia_python/blob/main/docs/CHANGELOG.md

v0.6.0 — rate-limit handling

Choose a tag to compare

@fschwar4 fschwar4 released this 10 Jun 14:43

Opt-in-by-default 429 retry (RetryPolicy + execute() seam), per-call retry= override, control-plane convergence. Amends ADR-0002 (a 429 on an idempotent call is now retried by default; opt out with SAIAClient(retry=False)). Full notes: docs/CHANGELOG.md.

v0.5.0

Choose a tag to compare

@fschwar4 fschwar4 released this 01 Jun 22:49

Highlights (full notes: docs/CHANGELOG.md → 0.5.0):

  • Per-file on_result hook on the upload/sync batch helpers (provenance/logging).
  • Default (connect, read) timeouts on control-plane calls — fail fast instead of
    hanging; generate_index(wait=True) tolerates + diagnoses transient poll timeouts.
  • New "Extensions" docs section + "ARCANA incremental sync" how-to.

v0.4.1

Choose a tag to compare

@fschwar4 fschwar4 released this 31 May 22:43

First release published to PyPI as
saia-python, together with
open-source project tooling and incremental-upload helpers for ARCANA.

Added

  • ArcanaService.upload_files(name, paths, *, overwrite=True, verbose=False)
    upload an explicit, caller-chosen list of files. The selection of what to
    (re)upload is the caller's (e.g. from a checksum/manifest comparison); reuses
    the same per-file batch reporting as upload_directory.
  • ArcanaService.sync_directory(name, directory, *, select, pattern="*", recursive=False, prune=False, index=True, index_wait=True, verbose=False)
    sync a local directory under a caller-supplied
    select(local_path, remote_or_None) -> "upload" | "replace" | "skip" policy,
    then trigger a single generate_index only when something changed. Keeps
    change-detection (e.g. SHA-256 vs. your own manifest) outside the package,
    since the ARCANA API exposes no content hash. Optional prune deletes remote
    files with no local counterpart.
  • PyPI packaging metadata in pyproject.toml: readme, license-files,
    keywords, trove classifiers, and an expanded [project.urls] table
    (Homepage, Documentation, Changelog, Issues) so the project page renders the
    README and is discoverable.
  • CITATION.cff (Citation File Format 1.2.0) — enables GitHub's "Cite this
    repository" button and import into reference managers.
  • saia_python/py.typed — PEP 561 marker so downstream type checkers consume
    the package's inline type hints; advertised via the Typing :: Typed
    classifier.
  • .github/workflows/publish.yml — builds the sdist + wheel, runs
    twine check --strict, and publishes to PyPI via OIDC Trusted Publishing
    when a GitHub Release is published (no stored API token).
  • README status badges (PyPI, Python versions, license, CI, docs) and a
    commented Zenodo DOI badge placeholder.
  • Linting and type-checking: ruff (lint + format) and mypy configuration in
    pyproject.toml, a lint optional-dependency group, a
    .pre-commit-config.yaml, and a Quality CI workflow
    (ruff check + ruff format --check + mypy).
  • Coverage reporting via pytest-cov; the Tests workflow now runs
    pytest --cov.

Changed

  • ArcanaService.list_files() now documents the full API FileOutSchema
    (name, size, owner_user_name, created_at, updated_at, index_info
    with per-file index_status / chunks_indexed, and related_files).
  • Corrected the license field from the deprecated SPDX identifier AGPL-3.0
    to AGPL-3.0-only. The license terms are unchanged; only the SPDX expression
    is now valid for PEP 639 / PyPI. Bumped the build requirement to
    setuptools>=77 (PEP 639 support).
  • Applied ruff autofixes and the formatter across the package and tests
    (Optional[X]X | None, import sorting, consistent formatting) plus minor
    mypy type-annotation fixes. No behavior change — all 92 tests pass. Known
    typing gaps (tomlkit's Item | Container in auth.py; the .list method
    shadowing the builtin in models.py / arcana.py) are scoped via documented
    per-module mypy overrides.