caselaw-access
Client SDK for the Case Law Explorer access service. One call decides authentication, endpoint scope, and rate limit, so a product does not implement any of them itself.
Install
pip install caselaw-access
For local development against unreleased changes, build a wheel and install it directly:
pip wheel . --no-deps -w dist
pip install dist/caselaw_access-*.whl
Prefer that over vendoring the wheel into a consumer repo: pip caches by version, so a rebuilt wheel with an unchanged version number is silently ignored.
Configuration
Everything comes from the environment:
| Variable | Meaning | Default |
|---|---|---|
ACCESS_SERVICE_URL |
Base URL of the access service. Empty disables all checks. | — |
ACCESS_PROJECT |
Project slug registered in the access console. | citations-api |
ACCESS_SERVICE_TIMEOUT_SECONDS |
Per-check timeout. | 5 |
ACCESS_FAIL_OPEN |
Waive quota for already-validated credentials when the service is unreachable. Never waives authentication. | false |
REQUIRE_AUTH / ACCESS_ENFORCE |
Master switch for enforcement. Unset on a connected deployment is an error, not an opt-out. | unset |
ACCESS_IDENTITY_CACHE_SECONDS |
How long a validated credential stays eligible for fail-open. | 300 |
Checks are only sent when the service URL is set and enforcement is on. That combination is what lets a service run locally, and its tests run in CI, without the control plane.
Direct use
from caselaw_access import AccessClient, RateLimited, PermissionDenied
client = AccessClient.from_env()
try:
decision = client.check(token, endpoint="echr", method="POST")
except RateLimited as exc:
... # exc.status == 429, exc.headers() has Retry-After
except PermissionDenied as exc:
... # exc.status == 403
An allowed Decision carries the resolved principal, the matched plan,
the granted scopes, and the remaining quota. decision.headers() returns
the X-RateLimit-Remaining-* headers to attach to the response.
http.server handlers
from caselaw_access.http_server import AccessGuard
guard = AccessGuard(send_error=my_json_writer)
class handler(BaseHTTPRequestHandler):
@guard.protect("echr", methods=["POST"])
def do_POST(self):
...
The decorated method runs only when the call is allowed; a denial is written
before the body executes. The decision is attached to the handler as
access_decision and access_principal, and the headers to emit as
access_headers.
The verb reported to the service is the request's real method
(handler.command). methods only shapes the CORS header on an error
response.
Failure semantics
| Situation | Result |
|---|---|
No or malformed Authorization header |
401, service never called |
| Service answers 401 | 401 with the service's code |
Service answers 403, or allowed: false with a scope error |
403 |
Service answers 429, or allowed: false with rate_limit_exceeded |
429 with Retry-After |
| Service unreachable, times out, or answers 4xx/5xx it cannot decide on | 503, or allowed when ACCESS_FAIL_OPEN=true and this credential was recently validated for this endpoint |
Connection failures are retried because the request provably never arrived. Read timeouts are not retried: the service may already have counted the call against the caller's quota, and a retry would charge them twice.
Behaviour is pinned by the shared conformance suite in
../contract, which this package runs in tests/test_conformance.py.
Releasing
Released by GitHub Actions via PyPI trusted publishing — no API token anywhere.
Bump version in pyproject.toml, commit, push, then:
git tag python-v0.2.0 && git push origin python-v0.2.0
The workflow runs this suite, including the shared conformance cases, before
uploading, and refuses to publish if the tag and pyproject.toml disagree. See
../README.md for the full procedure.
What fail-open does, and does not, do
/v1/check answers two questions at once: who is this caller, and do they
have quota left. Waiving both during an outage means any non-empty bearer
string is admitted — an authentication bypass dressed up as an availability
feature.
So ACCESS_FAIL_OPEN=true waives quota only, and only for a credential
this process has already watched the service accept for that same endpoint
within ACCESS_IDENTITY_CACHE_SECONDS. Anything else is denied exactly as if
the policy were fail-closed.
In practice: an outage does not interrupt traffic that was already flowing, and
does not let anything new in. The cost is that a credential revoked during an
outage keeps working until its cache entry expires — set
ACCESS_IDENTITY_CACHE_SECONDS=0 to disable the mechanism entirely, which
makes ACCESS_FAIL_OPEN a no-op.
The pair is keyed on credential and endpoint on purpose. Keying on the credential alone would let an outage promote a caller onto an endpoint they were never entitled to, turning a quota waiver into an authorization bypass.
Enforcement must be stated
Leaving REQUIRE_AUTH / ACCESS_ENFORCE unset while a project and a secret
are configured raises access_enforcement_not_configured rather than running
inert. A project secret is issued once, by hand, for a specific deployment, so
carrying one is unambiguous intent — an absent flag is a forgotten variable,
and guessing wrong means enforcing nothing while looking fully configured.
Set it explicitly to false to run inert on purpose.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file caselaw_access-0.2.0.tar.gz.
File metadata
- Download URL: caselaw_access-0.2.0.tar.gz
- Upload date:
- Size: 22.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f45ae9a8202a91bfb497a5c186ed444f62b805bf37dbfb8dc49e764134527309
|
|
| MD5 |
32615b1a7a0cf313f66b02689dd1b162
|
|
| BLAKE2b-256 |
030a6b799fb3205e3b679307237a356443b351209ae5152244c891f358651ddb
|
Provenance
The following attestation bundles were made for caselaw_access-0.2.0.tar.gz:
Publisher:
publish-python.yml on davidwickerhf/caselaw-access
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caselaw_access-0.2.0.tar.gz -
Subject digest:
f45ae9a8202a91bfb497a5c186ed444f62b805bf37dbfb8dc49e764134527309 - Sigstore transparency entry: 2256509169
- Sigstore integration time:
-
Permalink:
davidwickerhf/caselaw-access@25a20481b53e9ce06410fa8e5bc85265d2c82594 -
Branch / Tag:
refs/tags/python-v0.2.0 - Owner: https://github.com/davidwickerhf
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@25a20481b53e9ce06410fa8e5bc85265d2c82594 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caselaw_access-0.2.0-py3-none-any.whl.
File metadata
- Download URL: caselaw_access-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bab0af1f77f0b56529c49fde8f612affcfb0669d5f0be4e20e06dcd97e1f611f
|
|
| MD5 |
1dc69bdf1daf379049cac18d451fc005
|
|
| BLAKE2b-256 |
b5bea333a1b1bedc25a7a4acd4cc946d4dc8d65cfd6bda3611c038113636928f
|
Provenance
The following attestation bundles were made for caselaw_access-0.2.0-py3-none-any.whl:
Publisher:
publish-python.yml on davidwickerhf/caselaw-access
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caselaw_access-0.2.0-py3-none-any.whl -
Subject digest:
bab0af1f77f0b56529c49fde8f612affcfb0669d5f0be4e20e06dcd97e1f611f - Sigstore transparency entry: 2256509176
- Sigstore integration time:
-
Permalink:
davidwickerhf/caselaw-access@25a20481b53e9ce06410fa8e5bc85265d2c82594 -
Branch / Tag:
refs/tags/python-v0.2.0 - Owner: https://github.com/davidwickerhf
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@25a20481b53e9ce06410fa8e5bc85265d2c82594 -
Trigger Event:
push
-
Statement type: