Skip to main content

Typed, fail-closed authorization engine for Python 3.12+ with optional Strawberry, FastAPI, and SQLAlchemy adapters.

Project description

pyrmit

ci PyPI version Python versions License

Status: v0.x — experimental. API may change without notice.

A typed, fail-closed authorization engine for Python 3.12+. Strongly-typed generic policies, two-state decisions, and pluggable audit and entitlement providers. Optional adapters for Strawberry GraphQL, FastAPI, and SQLAlchemy.

Install

pip install pyrmit                                       # core only
pip install "pyrmit[strawberry,fastapi,sqlalchemy]"      # with adapters

The adapter extras pull in their respective frameworks; install only the ones you use.

Quickstart

from dataclasses import dataclass
from enum import StrEnum

from pyrmit import ALLOW, Decision, Entitlements, PolicyEngine, Principal, deny


class Action(StrEnum):
    READ = "read"


@dataclass(frozen=True)
class Actor:
    user_id: int
    is_admin: bool


@dataclass(frozen=True)
class Article:
    id: int
    owner_id: int
    is_published: bool


engine: PolicyEngine[Principal[Actor], Action, Article] = PolicyEngine()


@engine.policy(action=Action.READ, subject_type=Article)
def can_read_article(principal: Principal[Actor], article: Article) -> Decision:
    if principal.actor.is_admin:
        return ALLOW
    if article.is_published:
        return ALLOW
    if article.owner_id == principal.actor.user_id:
        return ALLOW
    return deny("article_unpublished")


alice = Principal[Actor](
    actor=Actor(user_id=42, is_admin=False),
    entitlements=Entitlements.empty(),
)
hidden = Article(id=1, owner_id=99, is_published=False)
decision = engine.decide(principal=alice, action=Action.READ, subject=hidden)
assert decision.allowed is False
assert decision.reason == "article_unpublished"

This example is exercised verbatim by tests/integration/typed_decisions/test_readme_quickstart.py to make sure the README never drifts from real behavior.

Key properties

  • Two-state Decisionallowed: bool plus a stable machine-readable reason. No third state.
  • Fail-closed — missing policy denies (policy_not_registered); a policy raising any exception denies (policy_error); audit-store failure denies (audit_unavailable) when audit_failure_mode="deny" (which requires audit_allows=True at engine construction so that ALLOW decisions are actually covered).
  • Defensively immutableDecision.detail and AuditEntry.metadata are wrapped in MappingProxyType at construction and reject non-primitive values at runtime.
  • Strict typingmypy --strict clean, no Any in core, no type: ignore. The decorator's subject_type binding is enforced by the type checker (see the negative test under tests/typing/).

Adapters

Adapters live under pyrmit.adapters.* and are optional extras:

  • Strawberry: policy_guard(...) — single-extension field guard with pre-resolution / from-source / post-resolution loader phases. NULL denial short-circuits before the resolver runs. post_resolution_policy_guard(...) is the explicit, safer counterpart for redaction-style use; it refuses to run inside a mutation operation by default (read_only=True) because the resolver runs before the authorization decision. PolicyGuardFactory(engine=..., principal_loader=...) is the recommended entry point for real schemas — it captures the engine and principal loader once and exposes .guard(...) / .post_resolution_guard(...) so individual fields don't restate the cross-cutting deps. See examples/strawberry_graphql/example_di.py for a DI-style wiring.
  • FastAPI: require_policy(...) — dependency factory that translates denials to HTTP responses; the NULL surface requires a null_mapper.
  • SQLAlchemy: visibility_scope(...) decorator marks a function as the per-actor row-level visibility predicate for a model; verify_scope_applied(...) is a tripwire test helper that checks the predicate is genuinely present on a compiled query.

Development

uv sync --all-extras
uv run pytest -q
uv run mypy src tests
uv run ruff check
uv run ruff format --check

See CONTRIBUTING.md for project conventions (PEP 695 generics, assertpy in tests, no Any outside the documented carve-outs, etc.) and SECURITY.md for the disclosure policy.

License

Apache-2.0. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrmit-0.2.0.tar.gz (42.0 kB view details)

Uploaded Source

Built Distribution

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

pyrmit-0.2.0-py3-none-any.whl (54.3 kB view details)

Uploaded Python 3

File details

Details for the file pyrmit-0.2.0.tar.gz.

File metadata

  • Download URL: pyrmit-0.2.0.tar.gz
  • Upload date:
  • Size: 42.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyrmit-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b3cabfa95fc3ec7be3a0b6949c384f5cf05d7fcfa6b8dfdb7ed437e851f972ca
MD5 9d3103472a82178332cc273d7dac3326
BLAKE2b-256 2f7c6225d9208c4436e6c49c4d021c4cc5f4638ac9bbba209558e173ee88fefb

See more details on using hashes here.

Provenance

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

Publisher: release.yml on cahna/pyrmit

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

File details

Details for the file pyrmit-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyrmit-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 54.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyrmit-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0439d05cb635863339de49dd78ff8df2a43af9b76850d0bb8923ebe4832b77ac
MD5 0eee891aa545b8cedaeab469f3456bef
BLAKE2b-256 137da26712eabbd280e1e9890f5de485e966c0ac398598c23a07e80ec9a289eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyrmit-0.2.0-py3-none-any.whl:

Publisher: release.yml on cahna/pyrmit

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

Supported by

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