Skip to content

0.1.5

Choose a tag to compare

@captainpacket captainpacket released this 09 Sep 16:02
· 36 commits to main since this release

Changed

  • The rate-limiting documentation now describes Forward's actual limiter, read
    from the server source rather than inferred from a plugin's constant. Three
    corrections matter. The budget is per authenticated user per minute, not
    per account, per host or per client, so every process using one set of
    credentials spends from one allowance. Exceeding it does not slow a request,
    it blocks the user for a lockout of one to sixty minutes, answered with 429
    and a Retry-After carrying the remaining block. And the ceiling is an org
    setting defaulting to 2000 and adjustable from 1 to 10,000, disabled entirely
    by default on self-hosted deployments, none of which the SDK can query. The
    "auto" default of 1800 is unchanged but is now described as headroom
    against a default rather than as a reading of your ceiling.

Added

  • to_api() on RepositoryQuery and DraftChange. These are frozen
    dataclasses rather than pydantic models, so they never had model_dump, and a
    consumer calling it saw their query index come back empty instead of failing:
    their normalisation swallowed the AttributeError, and an empty library is a
    plausible answer, so the symptom surfaced two calls later. The method is named
    to match ForwardModel.to_api so serializing something the SDK returned does
    not require knowing which of the two kinds it is.

    Note also that forward_sdk._generated.models declares its own
    RepositoryQuery, the wire schema these are built from. That one is a
    pydantic model and is not what the library methods return. The docstrings now
    say so.

  • ForwardResponseError, so a response the SDK cannot parse stays inside the
    exception tree. A body that failed validation previously raised pydantic's
    ValidationError, which is not a ForwardError and therefore travelled
    straight through every consumer's error handling: a sync died with a
    validation traceback in a job log rather than a failure it could classify and
    report. Reported by the NetBox integration, which hit it on a Network
    missing orgId.

    It carries the unparsed payload and the model_name the SDK tried to build,
    and keeps the original ValidationError as __cause__ for per-field detail.
    The wrapping is done once on the model base rather than at the 118 call sites
    that parse a response, because a caller's except ForwardError has to cover
    every one of them or it covers none.

    Required fields are unchanged and stay strict. They come from Forward's own
    generated description, and a model with a hole in it would carry a wrong
    response silently into whatever the caller writes next. The exposure is also
    smaller than it looks: 170 of 276 models require at least one field, 379 in
    total, and SnapshotInfo, Device, NqeRunResult and ApiVersion require
    none. The live suite passes unchanged against fwd.app, so real traffic carries
    what the description promises on every path it covers.

  • snapshot_cache_ttl, an opt-in cache for snapshot resolution, off by default.
    Covers latest_processed and latest_collected_id, keyed by the exact
    question so two tag scopes stay two answers, and cleared when an upload
    through the same client makes it wrong. client.snapshots.clear_cache()
    handles what the SDK cannot see.

    It exists for the rate limit rather than for latency. Forward's budget is per
    authenticated user and exceeding it blocks the user, so a sync resolving a
    snapshot once per slice across a thread pool spends a large share of an
    allowance on a question with one answer. It stays off by default because a
    stale snapshot does not raise, it returns real data from the wrong moment, and
    only the caller knows how long "latest" should stay true.

  • .github/workflows/live.yml runs the live tests weekly against a configured
    instance and opens an issue on failure. The unpublished endpoints have no
    generated description to check against, so nothing in CI could notice Forward
    changing one of their shapes; every such defect so far was reported by a
    consumer whose sync broke.

  • Six live tests under TestShapesWithNoCiBackstop, each pinning a shape that
    has been wrong at least once: rows surviving unrewritten, one page agreeing
    with all rows, committed source reaching only from a concrete commit, source
    without a path being refused before it is sent, a query carrying its commit,
    and the current user parsing.

Published through the release workflow with PyPI Trusted Publishing, so this is the first release whose files carry attestations.