Skip to main content

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

caselaw_access-0.2.0.tar.gz (22.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

caselaw_access-0.2.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

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

Hashes for caselaw_access-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f45ae9a8202a91bfb497a5c186ed444f62b805bf37dbfb8dc49e764134527309
MD5 32615b1a7a0cf313f66b02689dd1b162
BLAKE2b-256 030a6b799fb3205e3b679307237a356443b351209ae5152244c891f358651ddb

See more details on using hashes here.

Provenance

The following attestation bundles were made for caselaw_access-0.2.0.tar.gz:

Publisher: publish-python.yml on davidwickerhf/caselaw-access

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for caselaw_access-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bab0af1f77f0b56529c49fde8f612affcfb0669d5f0be4e20e06dcd97e1f611f
MD5 1dc69bdf1daf379049cac18d451fc005
BLAKE2b-256 b5bea333a1b1bedc25a7a4acd4cc946d4dc8d65cfd6bda3611c038113636928f

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.0

2 files

0.3.1

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page