Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Dogwood Policy Python SDK

Python SDK and PyO3 binding for the Dogwood policy language. Dogwood is a policy language for fine-grained authorization decisions that depend on history or patterns of events over time - not just a single request. It adds temporal conditions (since, formerly, once, aggregations) and information providers (computed guardrail facts) on top of Cedar policy syntax, then lowers everything back to Cedar for evaluation. Existing Cedar policies stay valid as-is.

GitHub Release GitHub Actions Test Workflow Status PyPI - Version Python Wheels Python Versions GitHub last commit PyPI - Status Conda Version License GitHub Downloads (all assets, all releases) PyPI Downloads

The public API is modeled after the Rust dogwood-language lifecycle:

  1. Build a ServiceSchema and PolicySchema.
  2. Parse and lower policy source into a LoweredPolicySet.
  3. Validate it.
  4. Feed Event values to a stateful Authorizer.

This package uses PyO3/maturin to bind the Rust dogwood-language reference for schema-backed lowering, validation, and trace replay. The Python SDK also keeps a temporary pure-Python fallback only for source-tree examples that omit a full Cedar action schema. Schema-backed workflows require the native extension.

Install For Development

The project uses a local .venv and maturin editable installs:

make setup
make develop
make test

The native extension imports as dogwood._dogwood_native; convenience wrappers live in dogwood.native.

Native vs. Non-Native Execution

This package has two execution paths.

Native path

The native path is the PyO3 extension built by maturin. It calls the Rust dogwood-language reference implementation.

Used for:

  • schema-backed policy lowering
  • schema-backed validation
  • schema-backed trace replay
  • augmented Cedar schema export

This is the path to use for compatibility with the Rust reference. It requires a real Cedar action schema.

You can check whether it is available:

from dogwood import native

assert native.available()

For repeated decisions, use a persistent native authorizer so policy lowering happens once:

from dogwood import native

authorizer = native.NativeAuthorizer(policy_source, cedar_schema_source)

decision = authorizer.authorize_request(
    "Drupe::Action::SellShares",
    'Drupe::OAuthUser::"alice"',
    'Drupe::Gateway::"trading"',
    {"shares": 25, "stock": "AMZN"},
)

assert decision == "Allow"

Non-native fallback

The fallback path is pure Python. It exists only so SDK examples can run without a full Cedar schema while the native API surface is still being built out.

It supports only a small subset:

  • basic permit / forbid
  • when / unless checks over context.*
  • simple trace parsing
  • simple formerly within temporal checks

It is not a replacement for the Rust reference implementation.

The SDK requires native behavior when a non-empty PolicySchema is supplied. It will raise a clear error if the native extension is missing. If the schema is empty, examples may still use the Python fallback.

Event Schema

Dogwood has two schema layers:

  • The policy/action schema is the Cedar .cedarschema file. It defines entities, actions, and request context types such as context.input.amount.
  • The event schema tells Dogwood how actions become historical events: which event kinds exist (request, response, error), which fields are recorded in the temporal history, and which event kinds produce authorization decisions.

The examples use Dogwood's default event schema. Under that default, request events are decision points, and the event history records request input fields plus reserved fields like callerPrincipal, callerResource, and requestId. That is why a temporal policy can ask about past events such as:

Drupe::Action::"Transfer"::request{ input.user: context.input.user }

In other words, the Cedar schema says what a Transfer request looks like; the event schema says that Transfer::request is both authorizable and stored in history for later temporal checks.

Python API

from dogwood import Authorizer, Event, LoweredPolicySet, PolicySchema, ServiceSchema

policy = '''
@id("sell_small_only")
permit (
    principal,
    action == Drupe::Action::"SellShares",
    resource
)
when { context.input.shares <= 50 };
'''

policies = LoweredPolicySet.from_str(policy, ServiceSchema.defaults(), PolicySchema(""))
authorizer = Authorizer(policies)

event = (
    Event.builder('Drupe::Action::"SellShares"', "request")
    .principal('Drupe::OAuthUser::"alice"')
    .resource('Drupe::Gateway::"gw1"')
    .field("input", "shares", 50)
    .request_context("input", "shares", 50)
    .build()
)

assert authorizer.is_authorized(event).allowed()

There is also a runnable example:

make example

FastAPI Native Example

The FastAPI example uses the native binding and a real Cedar schema. It loads its own examples/fastapi_simple/policy.dw and examples/fastapi_simple/schema.cedarschema, creates a persistent native.NativeAuthorizer, and exposes an authorization endpoint.

The policy enforces a $50 daily transfer limit per user. Three $20 transfers by the same user produce:

Allow, Allow, Deny

Run it:

make develop
make examples-deps
make fastapi-example

First transfer:

curl -s http://127.0.0.1:8000/authorize \
  -H 'content-type: application/json' \
  -d '{"user":"alice","amount":20}'

Expected response:

{"decision":"Allow","allowed":true,"daily_limit":50}

Second transfer:

curl -s http://127.0.0.1:8000/authorize \
  -H 'content-type: application/json' \
  -d '{"user":"alice","amount":20}'

Expected response:

{"decision":"Allow","allowed":true,"daily_limit":50}

Third transfer:

curl -s http://127.0.0.1:8000/authorize \
  -H 'content-type: application/json' \
  -d '{"user":"alice","amount":20}'

Expected response:

{"decision":"Deny","allowed":false,"daily_limit":50}

Dogwood authorizers are stateful. The example keeps one shared native authorizer and protects it with a lock. For high-throughput services, use a pool or request-partitioned authorizers based on your temporal semantics.

The same FastAPI app also includes a quota-based rate limit endpoint:

curl -s http://127.0.0.1:8000/authorize/quota \
  -H 'content-type: application/json' \
  -d '{"user":"carol","amount":1}'

That endpoint uses examples/fastapi_simple/quota_policy.dw, which permits fewer than three transfers by the same user within one hour. For one user, the first two requests are allowed and the third is denied.

CLI

dogwood-py validate policy.dw --policy-schema schema.cedarschema
dogwood-py replay policy.dw --policy-schema schema.cedarschema --trace trace.log
dogwood-py lower policy.dw --policy-schema schema.cedarschema

When using the local virtualenv directly:

.venv/bin/dogwood-py validate policy.dw --policy-schema schema.cedarschema

Run the checked-in CLI example:

make cli-example

Equivalent command:

.venv/bin/dogwood-py replay examples/cli/policy.dw \
  --policy-schema examples/cli/schema.cedarschema \
  --trace examples/cli/trace.log

Expected output:

@0 (time point 0): true
@1 (time point 1): false

Make Targets

  • make setup creates .venv and installs development tools.
  • make develop builds and installs the PyO3 extension in editable mode.
  • make examples-deps installs optional dependencies used by examples.
  • make test runs the Python test suite.
  • make perf-test runs the opt-in native-vs-Python replay performance check.
  • make example runs examples/api_usage.py.
  • make cli-example runs the dogwood-py replay example.
  • make fastapi-example starts the native-backed FastAPI server.
  • make build builds a wheel with maturin.
  • make clean removes generated caches and Rust build output.

The performance test is a coarse regression guard, not a precise benchmark. It uses DOGWOOD_PERF_TESTS=1, prints native and pure-Python replay timings, and asserts the Rust-backed end-to-end path is not catastrophically slower than the fallback on the same generated trace. Tune the ceiling with DOGWOOD_NATIVE_MAX_RATIO when needed.

Latest local performance check:

native replay: 0.4793s
python fallback replay: 0.0708s
ratio native/python: 6.77

persistent native authorizer: 0.4424s
python fallback authorizer: 0.0147s
ratio native/python: 30.14

This result does not mean the Rust implementation is slower in general. The test compares the full native Dogwood path, including real schema-backed Rust lowering/replay semantics, against the intentionally minimal Python fallback. The value of the test is detecting large accidental regressions in the binding path, not benchmarking the Rust engine in isolation.

The persistent native authorizer avoids repeated policy lowering, but each request still crosses the Python/Rust boundary, converts Python input into Dogwood values, and runs the full Cedar-backed decision path. The fallback remains much faster for this tiny policy because it evaluates only a narrow regex-parsed subset with no real Cedar schema semantics.

GitHub Actions

The repository includes workflows adapted for this PyO3/maturin package:

  • Tests runs on pull requests and pushes to main. It builds the native extension with maturin develop, runs pytest, and checks the CLI example across Python 3.10-3.13 on Linux and macOS.
  • Build and Release runs on pull requests and pushes to main. Pull requests build source/wheel artifacts for review. Pushes to main build the same artifacts and create a GitHub release.
  • Security Checks runs workflow linting, dependency review, hidden Unicode scanning, and flags workflow-file changes for review.
  • Dependabot checks GitHub Actions, Cargo, and Python dependency updates weekly.

PyPI publishing is opt-in. Configure trusted publishing for the pypi environment and set the repository variable PUBLISH_PYPI=true to publish distributions on pushes to main.

Current Scope

Rust-backed operations cover schema-backed lowering, validation, and trace replay. The Python fallback is temporary and schema-less only. The intended end state is to remove it once the Rust-backed SDK objects cover the same ergonomic surface.

Download files

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

Source Distribution

dogwood_py-0.0.1.dev7.tar.gz (33.1 kB view details)

Uploaded Source

Built Distributions

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

dogwood_py-0.0.1.dev7-cp310-abi3-win_amd64.whl (5.4 MB view details)

Uploaded CPython 3.10+Windows x86-64

dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (6.1 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (5.9 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

dogwood_py-0.0.1.dev7-cp310-abi3-macosx_11_0_arm64.whl (5.3 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

dogwood_py-0.0.1.dev7-cp310-abi3-macosx_10_12_x86_64.whl (5.6 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file dogwood_py-0.0.1.dev7.tar.gz.

File metadata

  • Download URL: dogwood_py-0.0.1.dev7.tar.gz
  • Upload date:
  • Size: 33.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dogwood_py-0.0.1.dev7.tar.gz
Algorithm Hash digest
SHA256 0d2db986220e43d2c08930e0f22d2789d9b66e992f781d954ef6175a86e05413
MD5 8634d053ba4e25c83159195364c7fb99
BLAKE2b-256 a29faa3ddde4a14e3dfc5f1acc33da04ad67d90534776d0668bd5b5b8a8e2b28

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7.tar.gz:

Publisher: release.yml on abhishektiwari/dogwood-py

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

File details

Details for the file dogwood_py-0.0.1.dev7-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev7-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 f20c231667d8981e6cbab63bce064b7a48d70914725194b481f9a15d918f016e
MD5 d40aa9da003da13acc998a33acca277b
BLAKE2b-256 d828079df02978f0fd2b7710db096be6ebbc3a7acc15c4e29c059f13e6affba1

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7-cp310-abi3-win_amd64.whl:

Publisher: release.yml on abhishektiwari/dogwood-py

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

File details

Details for the file dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 811d7521c85cbcb1f02b0d4ac100aa7b87a6196a3916b46be8afc056366c81b9
MD5 9051552e59ee494159502adac7c4ad1a
BLAKE2b-256 6cf3a5e58ddedea1a2d81207a82099271b63858d24f933f955e64c77d23d8a71

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on abhishektiwari/dogwood-py

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

File details

Details for the file dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 41c11f0067f5c250b4a85d1d013817e31a9232b0fffc3880824f0cbed6498e5c
MD5 75e7f73e48235de25408c619eb54925f
BLAKE2b-256 48c218130146c74952222ee32e2f970f3dd825809844829e9b3f8c4f6e56bb71

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on abhishektiwari/dogwood-py

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

File details

Details for the file dogwood_py-0.0.1.dev7-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev7-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 bc3a4f6429b4182a512a85e2270ad40c7997f3edeb8eb2e745bf8cb38f15940a
MD5 76ba4d3f08ec232d7835eff771dd0510
BLAKE2b-256 9dd736776afe0f2add56f84a834d54a63ae1c3b57b5e720390a6722bc4c505a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on abhishektiwari/dogwood-py

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

File details

Details for the file dogwood_py-0.0.1.dev7-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev7-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 1ce4e8711a3ca2fe21f2a35e9594d2bef371fcac37ec07e258b1f203046c39c7
MD5 7b6772274dbb3e6eb595b9db60703424
BLAKE2b-256 cb4ff09966d0de3d48bcb9f22e0c01957f96ab400fccfaa21c219ba095a12de8

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev7-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on abhishektiwari/dogwood-py

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