speedle
Python SDK for Speedle authorization checks: is-allowed against
Speedle Cloud and self-hosted
Speedle+. Python 3.10 or later,
sync and asyncio clients, fully typed. The only runtime dependency is
httpx. The behaviour follows docs/design.md.
Not published yet. Version 0.1.0 is in development.
Install
pip install speedle
Quickstart: Speedle Cloud
import datetime
import os
from speedle import Attribute, Auth, Client
with Client(
backend="speedle-cloud",
endpoint="https://cloud.speedle.io",
auth=Auth.bearer(os.environ["SPEEDLE_TOKEN"]),
service_name="books", # the token's service
) as speedle:
if speedle.is_allowed(
"alice",
"get",
"/books/123",
[
Attribute.string("team", "eng"),
Attribute.numeric("clearance", 4),
Attribute.datetime("issued_at", datetime.datetime(2026, 11, 5, 3, tzinfo=datetime.timezone.utc)),
],
):
...
Quickstart: Speedle+ with asyncio
from speedle import AsyncClient, Auth
async with AsyncClient(
backend="speedle-plus",
endpoint="https://ads.internal:6734",
auth=Auth.none(), # or Auth.mutual_tls("client.pem", "client.key") with --force-client-cert
ca_file="ca.pem", # extra trust anchor for a private CA
service_name="books",
) as speedle:
decision = await speedle.check("alice", "get", "/books/123", {"team": "eng", "clearance": 4})
# Decision(allowed=False, reason=<Reason.NO_APPLICABLE_POLICIES: 3>, ...)
http:// is accepted only for speedle-plus with Auth.none(), and only for
loopback hosts unless you pass allow_insecure_http=True. Speedle+ warns that
an unauthenticated plaintext endpoint lets anyone on the network ask for
decisions; prefer HTTPS.
API
Client(...)andAsyncClient(...)take the same keyword arguments. Create one per endpoint and share it; it is thread-safe and owns one pooledhttpxclient. Use it as a context manager, or callclose()/aclose().is_allowed(subject, action, resource, attributes=None, *, deadline=None, service_name=None)returns the server'sallowedvalue.check(...)returns aDecision:allowed,reason(aReason, or an unknown integer as is),error_message(Speedle+ evaluator errors) andquota_daily_remaining(Speedle Cloud).subjectis a user name or a list ofPrincipal(name, type="user", idd=None)(PrincipalTypehas the Speedle+ values).attributesis either a list built withAttribute.string,Attribute.numeric,Attribute.boolean(typebool) andAttribute.datetime(each takes one value or a list), or a mapping whose types are inferred:str,int/float,bool(checked beforeint), time-zone-awaredatetime, or a non-empty list of one of them. Astris always a string, even if it looks like a date. Inferred attributes are sent sorted by name. Invalid values (empty or duplicate names,None, NaN, infinities,boolas a number, naive datetimes, mixed or empty inferred lists) raiseInvalidRequestErrorbefore any request. Numbers above 2^53 lose precision, because Speedle+ decodes numbers asfloat64.service_name=overrides the client's service for one call. On Speedle Cloud a service other than the token's fails withForbiddenError(codeservice_mismatch); that call is charged.deadline=replaces the client's deadline (seconds) for one call.AsyncClienthonours task cancellation andasyncio.timeout(), also during a retry delay;asyncio.CancelledErroris re-raised in every failure mode.
Reason.DISCOVER_MODE (5) returns allowed=True but is not a production
grant.
Retries and deadlines
RetryPolicy field (seconds) |
Default |
|---|---|
max_attempts (counts the first; 1 disables retries) |
3 |
base_delay |
0.1 |
max_delay |
2.0 |
attempt_timeout |
2.0 |
deadline (the whole call) |
5.0 |
Backoff is exponential with full jitter, raised to Retry-After when the
server sends one. Retried: connection errors, attempt timeouts, 502, 503 and
504, and a burst 429 whose Retry-After fits the remaining deadline. Never
retried: 400, 401, 403, 404, the daily-quota 429, 500 and other 5xx, 413, 415,
TLS failures and unexpected bodies. Speedle Cloud charges every call that
reaches its handler, including a retried snapshot_unavailable 503, so a
call costs at most max_attempts units.
In the sync client, attempt_timeout bounds each phase of an attempt
(connect, write and each read), because httpx has no single total timeout
for blocking requests. The async client bounds the whole attempt.
Errors
Every error extends SpeedleError and carries status, code (Speedle Cloud
error code), message and quota_daily_remaining. The types are
ConfigurationError, InvalidRequestError, UnauthorizedError,
ForbiddenError, RequestTooLargeError, TooManyRequestsError (with
retry_after in seconds; subtypes RateLimitedError and
QuotaExceededError), ServiceUnavailableError, ServerError, HttpError,
UnexpectedResponseError, NetworkError (subtype speedle.TimeoutError,
whose subtype is DeadlineExceededError) and CircuitOpenError.
speedle.TimeoutError is the SDK's own class, not the built-in.
Failure mode
failure_mode="closed" makes is_allowed return False instead of raising
any SDK error, including CircuitOpenError and validation errors;
on_give_up still reports the error. check always raises. Constructor
errors and cancellation are never converted. There is no fail-open mode:
is_allowed returns True only for a 200 response whose allowed is true.
Circuit breaker
Off by default. circuit_breaker=CircuitBreaker(failure_threshold=5, cool_down=30.0)
opens after that many consecutive failed calls (network errors, timeouts and
5xx), fails fast with CircuitOpenError during the cool-down, then lets one
trial call through.
Hooks
hooks=Hooks(on_attempt=..., on_retry=..., on_give_up=..., on_breaker_state_change=...)
are called synchronously; exceptions and return values are ignored. Payloads
never contain the token, headers, subject, action, resource or attributes.
The SDK sends no telemetry and does no logging; it sends only the
is-allowed request, with
User-Agent: speedle-sdk-python/<version> (python/<version>; <os>) plus
user_agent_suffix. Proxy, netrc and certificate settings from environment
variables are not used.
Security
- The Speedle Cloud token is a server-side secret. Never ship it to browsers, mobile apps or desktop clients.
Auth.bearerrequires HTTPS. The token is redacted fromrepr, hook payloads and error messages, andAuthcannot be pickled.- Redirects are never followed. TLS verification cannot be disabled.
- Take subjects and attributes from your own authenticated context, never from untrusted client input.
- Decisions are never cached.
Development
uv sync
uv run ruff check && uv run ruff format --check && uv run mypy
uv run pytest tests --ignore=tests/conformance # unit tests
uv run pytest tests/conformance # every vector in ../conformance/vectors
License
UPL-1.0. Copyright (c) 2026 The Speedle Authors.
Metadata
Release files for speedle 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| speedle-0.1.0.tar.gz | 18.8 kB | With provenance | Details for speedle-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| speedle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for speedle-0.1.0-py3-none-any.whl |
Total release size: 41.7 kB
Release files / speedle-0.1.0.tar.gz
| Download URL | speedle-0.1.0.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99734038f3e962b4fdd64160f5bee646e2b97b1d4602d37342cade400d066ba9
|
|
BLAKE2b-256 checksum How to use checksums |
a2f5c29cb2a01012dc0c0a1c8bd00e92447557d4810c6837df6b6620ed3f8f93
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 5, 2026.
Transparency logRelease files / speedle-0.1.0-py3-none-any.whl
| Download URL | speedle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 22.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
faf2833eb97f9901fa404389f9d83fd9eb036c76d4dd86fa48c37862d6b8560e
|
|
BLAKE2b-256 checksum How to use checksums |
14ceb15969c10b73e9a453980693d90f308cd0c7c5a40f5e9ee0d7efba111fa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 5, 2026.
Transparency logSecurity for speedle 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
All 2 files in this release have been uploaded with provenance metadata.
Attestation for speedle-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 Nov 5, 2026.
Transparency logA 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.