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.

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

Equivalent commands:

python -m venv .venv
.venv/bin/python -m pip install -U pip maturin pytest
.venv/bin/maturin develop
.venv/bin/python -m pytest -q

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 Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

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

Uploaded CPython 3.10+Windows x86-64

dogwood_py-0.0.1.dev5-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.dev5-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.dev5-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.dev5-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.dev5-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev5-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 11ffa57d73cc6cc2e7898ba7b08dbe0d50447f1e0c8d58ed2519a5ad4955068c
MD5 35a4652a850717d0e6e9415bb0d743d1
BLAKE2b-256 1392fc421fd5d70e5374f6e2f20e09fbbce3d9f9f1cb2bcd67c9534d28a97ff4

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev5-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.dev5-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev5-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7eb322abee4690cd9b19910c0e496b8e3652b179ec7d6a423c469bce8bb0f796
MD5 fe54615156b16611c19e91b8ec344000
BLAKE2b-256 f85ec67d5f51b7392d07a3a61a19fb40bb496e8e3d2466c9412b13e9ee1c8d1a

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev5-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.dev5-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev5-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 cc0a31622982a8a267e62f1ff7ea98832b91d69358b09459cf8ab0ab837456ce
MD5 2998a18fff3935fe1250e9d4b4d6080b
BLAKE2b-256 61f30a5444471217d45f3dc5b133d68677e6a0be364384d26c36763ba8dcbc9c

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev5-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.dev5-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev5-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0eb5033db06d3f672b5c28c905bd70a0dbae3cb5a22da985061501f43c671c96
MD5 a7e9acb0731b8cfaf29d06ed6de6dc10
BLAKE2b-256 96deccd9568e29876c745b56f95d687ba56b9809d9325578b2ea812be8312396

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev5-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.dev5-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for dogwood_py-0.0.1.dev5-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e4860a884808f3d5ce85fb3cfdc609e6108bb8327eae92814e41661b937dd218
MD5 4dc1d42ed17a6b5589a8fceade87a750
BLAKE2b-256 2877200fc32af0704fbe7bcc6569c77e0a6ec8d80e2a06b6ebb13e7746a77d51

See more details on using hashes here.

Provenance

The following attestation bundles were made for dogwood_py-0.0.1.dev5-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